Merge "Don't remove SYSTEM_FIXED for intentional fixed grants to critical apps."
diff --git a/Android.bp b/Android.bp
index 95cdea0..acd3b34 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,6 +25,18 @@
 //
 // READ ME: ########################################################
 
+// TODO(b/21090328): Remove filter after we are ready to.
+soong_config_module_type {
+    name: "java_library_with_nonpublic_deps",
+    module_type: "java_library",
+    config_namespace: "ANDROID",
+    bool_variables: ["include_nonpublic_framework_api"],
+    properties: [
+        "static_libs",
+        "libs",
+    ],
+}
+
 package {
     default_applicable_licenses: ["frameworks_base_license"],
 }
@@ -69,7 +81,7 @@
         // Java/AIDL sources under frameworks/base
         ":framework-annotations",
         ":framework-blobstore-sources",
-        ":framework-connectivity-nsd-sources",
+        ":framework-connectivity-tiramisu-sources",
         ":framework-core-sources",
         ":framework-drm-sources",
         ":framework-graphics-nonupdatable-sources",
@@ -142,7 +154,7 @@
     ],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "framework-updatable-stubs-module_libs_api",
     static_libs: [
         "android.net.ipsec.ike.stubs.module_lib",
@@ -161,11 +173,18 @@
         "framework-uwb.stubs.module_lib",
         "framework-wifi.stubs.module_lib",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs.module_lib",
+            ],
+        },
+    },
     sdk_version: "module_current",
     visibility: ["//visibility:private"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "framework-all",
     installable: false,
     static_libs: [
@@ -186,6 +205,13 @@
         "framework-wifi.impl",
         "updatable-media",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs.module_lib",
+            ],
+        },
+    },
     apex_available: ["//apex_available:platform"],
     sdk_version: "core_platform",
     visibility: [
@@ -362,6 +388,7 @@
         "tv_tuner_resource_manager_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
         "modules-utils-preconditions",
+        "modules-utils-synchronous-result-receiver",
         "modules-utils-os",
         "framework-permission-aidl-java",
         "spatializer-aidl-java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 3b11036..9543fbd 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -23,6 +23,14 @@
 // and comparing them against the checked in API signature, and also checking compatibility
 // with the latest frozen API signature.
 
+// TODO(b/21090328): Remove filter after we are ready to.
+soong_config_module_type_import {
+    from: "frameworks/base/Android.bp",
+    module_types: [
+        "java_library_with_nonpublic_deps",
+    ],
+}
+
 /////////////////////////////////////////////////////////////////////
 // Common metalava configs
 /////////////////////////////////////////////////////////////////////
@@ -299,21 +307,35 @@
     visibility: ["//visibility:private"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android-non-updatable.stubs",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":api-stubs-docs-non-updatable"],
     libs: modules_public_stubs,
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     dist: {
         dir: "apistubs/android/public",
     },
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android-non-updatable.stubs.system",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":system-api-stubs-docs-non-updatable"],
     libs: modules_system_stubs,
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     dist: {
         dir: "apistubs/android/system",
     },
@@ -334,11 +356,18 @@
     },
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android-non-updatable.stubs.test",
     defaults: ["android-non-updatable_defaults_stubs_current"],
     srcs: [":test-api-stubs-docs-non-updatable"],
     libs: modules_system_stubs,
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     dist: {
         dir: "apistubs/android/test",
     },
@@ -357,21 +386,35 @@
     defaults_visibility: ["//frameworks/base/services"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android_stubs_current",
     static_libs: modules_public_stubs + [
         "android-non-updatable.stubs",
         "private-stub-annotations-jar",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     defaults: ["android.jar_defaults"],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     name: "android_system_stubs_current",
     static_libs: modules_system_stubs + [
         "android-non-updatable.stubs.system",
         "private-stub-annotations-jar",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
@@ -392,7 +435,7 @@
     ],
 }
 
-java_library {
+java_library_with_nonpublic_deps {
     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.
@@ -400,6 +443,13 @@
         "android-non-updatable.stubs.test",
         "private-stub-annotations-jar",
     ],
+    soong_config_variables: {
+        include_nonpublic_framework_api: {
+            static_libs: [
+                "framework-supplementalapi.stubs",
+            ],
+        },
+    },
     defaults: [
         "android.jar_defaults",
         "android_stubs_dists_default",
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
index 973e996..762b16c 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -24,19 +24,17 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
-
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
-
+import java.io.File;
+import java.util.Random;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Random;
-
 /**
  * Performance tests for typical CRUD operations and loading rows into the Cursor
  *
@@ -58,12 +56,9 @@
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
         mContext.deleteDatabase(DB_NAME);
-        mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
-        mDatabase.execSQL("CREATE TABLE T1 "
-                + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
-        mDatabase.execSQL("CREATE TABLE T2 ("
-                + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
-                + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+
+        createOrOpenTestDatabase(
+                SQLiteDatabase.JOURNAL_MODE_TRUNCATE, SQLiteDatabase.SYNC_MODE_FULL);
     }
 
     @After
@@ -72,6 +67,25 @@
         mContext.deleteDatabase(DB_NAME);
     }
 
+    private void createOrOpenTestDatabase(String journalMode, String syncMode) {
+        SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder();
+        File dbFile = mContext.getDatabasePath(DB_NAME);
+        if (journalMode != null) {
+            paramsBuilder.setJournalMode(journalMode);
+        }
+        if (syncMode != null) {
+            paramsBuilder.setSynchronousMode(syncMode);
+        }
+        paramsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
+
+        mDatabase = SQLiteDatabase.openDatabase(dbFile, paramsBuilder.build());
+        mDatabase.execSQL("CREATE TABLE T1 "
+                + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+        mDatabase.execSQL("CREATE TABLE T2 ("
+                + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+                + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+    }
+
     @Test
     public void testSelect() {
         insertT1TestDataSet();
@@ -192,22 +206,114 @@
         }
     }
 
+    /**
+     * This test measures the insertion of a single row into a database using DELETE journal and
+     * synchronous modes.
+     */
     @Test
     public void testInsert() {
         insertT1TestDataSet();
 
+        testInsertInternal("testInsert");
+    }
+
+    @Test
+    public void testInsertWithPersistFull() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
+        insertT1TestDataSet();
+        testInsertInternal("testInsertWithPersistFull");
+    }
+
+    private void testInsertInternal(String traceTag) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
 
         ContentValues cv = new ContentValues();
         cv.put("_ID", DEFAULT_DATASET_SIZE);
         cv.put("COL_B", "NewValue");
         cv.put("COL_C", 1.1);
-        String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+        String[] deleteArgs = new String[] {String.valueOf(DEFAULT_DATASET_SIZE)};
+
         while (state.keepRunning()) {
+            android.os.Trace.beginSection(traceTag);
             assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
             state.pauseTiming();
             assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
             state.resumeTiming();
+            android.os.Trace.endSection();
+        }
+    }
+
+    /**
+     * This test measures the insertion of a single row into a database using WAL journal mode and
+     * NORMAL synchronous mode.
+     */
+    @Test
+    public void testInsertWithWalNormalMode() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+        insertT1TestDataSet();
+
+        testInsertInternal("testInsertWithWalNormalMode");
+    }
+
+    /**
+     * This test measures the insertion of a single row into a database using WAL journal mode and
+     * FULL synchronous mode. The goal is to see the difference between NORMAL vs FULL sync modes.
+     */
+    @Test
+    public void testInsertWithWalFullMode() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_FULL);
+
+        insertT1TestDataSet();
+
+        testInsertInternal("testInsertWithWalFullMode");
+    }
+
+    /**
+     * This test measures the insertion of a multiple rows in a single transaction using WAL journal
+     * mode and NORMAL synchronous mode.
+     */
+    @Test
+    public void testBulkInsertWithWalNormalMode() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+        testBulkInsertInternal("testBulkInsertWithWalNormalMode");
+    }
+
+    @Test
+    public void testBulkInsertWithPersistFull() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
+        testBulkInsertInternal("testBulkInsertWithPersistFull");
+    }
+
+    /**
+     * This test measures the insertion of a multiple rows in a single transaction using TRUNCATE
+     * journal mode and FULL synchronous mode.
+     */
+    @Test
+    public void testBulkInsert() {
+        testBulkInsertInternal("testBulkInsert");
+    }
+
+    private void testBulkInsertInternal(String traceTag) {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+        String[] statements = new String[DEFAULT_DATASET_SIZE];
+        for (int i = 0; i < DEFAULT_DATASET_SIZE; ++i) {
+            statements[i] = "INSERT INTO T1 VALUES (?,?,?,?)";
+        }
+
+        while (state.keepRunning()) {
+            android.os.Trace.beginSection(traceTag);
+            mDatabase.beginTransaction();
+            for (int i = 0; i < DEFAULT_DATASET_SIZE; ++i) {
+                mDatabase.execSQL(statements[i], new Object[] {i, i, "T1Value" + i, i * 1.1});
+            }
+            mDatabase.setTransactionSuccessful();
+            mDatabase.endTransaction();
+            android.os.Trace.endSection();
+
+            state.pauseTiming();
+            mDatabase.execSQL("DELETE FROM T1");
+            state.resumeTiming();
         }
     }
 
@@ -227,9 +333,30 @@
         }
     }
 
+    /**
+     * This test measures the update of a random row in a database.
+     */
+    @Test
+    public void testUpdateWithWalNormalMode() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+        insertT1TestDataSet();
+        testUpdateInternal("testUpdateWithWalNormalMode");
+    }
+
+    @Test
+    public void testUpdateWithPersistFull() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
+        insertT1TestDataSet();
+        testUpdateInternal("testUpdateWithPersistFull");
+    }
+
     @Test
     public void testUpdate() {
         insertT1TestDataSet();
+        testUpdateInternal("testUpdate");
+    }
+
+    private void testUpdateInternal(String traceTag) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
 
         Random rnd = new Random(0);
@@ -237,6 +364,7 @@
         ContentValues cv = new ContentValues();
         String[] argArray = new String[1];
         while (state.keepRunning()) {
+            android.os.Trace.beginSection(traceTag);
             int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
             cv.put("COL_A", i);
             cv.put("COL_B", "UpdatedValue");
@@ -244,6 +372,109 @@
             argArray[0] = String.valueOf(id);
             assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
             i++;
+            android.os.Trace.endSection();
+        }
+    }
+
+    /**
+     * This test measures a multi-threaded read-write environment where there are 2 readers and
+     * 1 writer in the database using TRUNCATE journal mode and FULL syncMode.
+     */
+    @Test
+    public void testMultithreadedReadWrite() {
+        insertT1TestDataSet();
+        performMultithreadedReadWriteTest();
+    }
+
+    private void doReadLoop(int totalIterations) {
+        Random rnd = new Random(0);
+        int currentIteration = 0;
+        while (currentIteration < totalIterations) {
+            android.os.Trace.beginSection("ReadDatabase");
+            int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+                                 + "WHERE _ID=?",
+                         new String[] {String.valueOf(index)})) {
+                cursor.moveToNext();
+                cursor.getInt(0);
+                cursor.getInt(1);
+                cursor.getString(2);
+                cursor.getDouble(3);
+            }
+            ++currentIteration;
+            android.os.Trace.endSection();
+        }
+    }
+
+    private void doReadLoop(BenchmarkState state) {
+        Random rnd = new Random(0);
+        while (state.keepRunning()) {
+            android.os.Trace.beginSection("ReadDatabase");
+            int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+                                 + "WHERE _ID=?",
+                         new String[] {String.valueOf(index)})) {
+                cursor.moveToNext();
+                cursor.getInt(0);
+                cursor.getInt(1);
+                cursor.getString(2);
+                cursor.getDouble(3);
+            }
+            android.os.Trace.endSection();
+        }
+    }
+
+    private void doUpdateLoop(int totalIterations) {
+        SQLiteDatabase db = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+        Random rnd = new Random(0);
+        int i = 0;
+        ContentValues cv = new ContentValues();
+        String[] argArray = new String[1];
+
+        while (i < totalIterations) {
+            android.os.Trace.beginSection("UpdateDatabase");
+            int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+            cv.put("COL_A", i);
+            cv.put("COL_B", "UpdatedValue");
+            cv.put("COL_C", i);
+            argArray[0] = String.valueOf(id);
+            db.update("T1", cv, "_ID=?", argArray);
+            i++;
+            android.os.Trace.endSection();
+        }
+    }
+
+    /**
+     * This test measures a multi-threaded read-write environment where there are 2 readers and
+     * 1 writer in the database using WAL journal mode and NORMAL syncMode.
+     */
+    @Test
+    public void testMultithreadedReadWriteWithWalNormal() {
+        recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+        insertT1TestDataSet();
+
+        performMultithreadedReadWriteTest();
+    }
+
+    private void performMultithreadedReadWriteTest() {
+        int totalBGIterations = 10000;
+        // Writer - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
+        Thread updateThread = new Thread(() -> { doUpdateLoop(totalBGIterations); });
+
+        // Reader 1 - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
+        Thread readerThread = new Thread(() -> { doReadLoop(totalBGIterations); });
+
+        updateThread.start();
+        readerThread.start();
+
+        // Reader 2
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        doReadLoop(state);
+
+        try {
+            updateThread.join();
+            readerThread.join();
+        } catch (Exception e) {
         }
     }
 
@@ -270,5 +501,11 @@
         mDatabase.setTransactionSuccessful();
         mDatabase.endTransaction();
     }
+
+    private void recreateTestDatabase(String journalMode, String syncMode) {
+        mDatabase.close();
+        mContext.deleteDatabase(DB_NAME);
+        createOrOpenTestDatabase(journalMode, syncMode);
+    }
 }
 
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
index ba92d95..73ef310 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
@@ -48,6 +48,7 @@
         mLeaseInfos = leaseInfos;
     }
 
+    @SuppressWarnings("UnsafeParcelApi")
     private BlobInfo(Parcel in) {
         mId = in.readLong();
         mExpiryTimeMs = in.readLong();
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 9c0c365..66767e2 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -1408,6 +1408,7 @@
          * Use the {@link #CREATOR}
          * @hide
          */
+        @SuppressWarnings("UnsafeParcelApi")
         AlarmClockInfo(Parcel in) {
             mTriggerTime = in.readLong();
             mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader());
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 630d5ce..b9673f2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -881,6 +881,7 @@
         return hashCode;
     }
 
+    @SuppressWarnings("UnsafeParcelApi")
     private JobInfo(Parcel in) {
         jobId = in.readInt();
         extras = in.readPersistableBundle();
@@ -1542,11 +1543,25 @@
          *
          * <p>Note that trigger URIs can not be used in combination with
          * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
-         * for content changes, you need to schedule a new JobInfo observing the same URIs
-         * before you finish execution of the JobService handling the most recent changes.
+         * for content changes, you need to schedule a new JobInfo using the same job ID and
+         * observing the same URIs in place of calling
+         * {@link JobService#jobFinished(JobParameters, boolean)}. Remember that
+         * {@link JobScheduler#schedule(JobInfo)} stops a running job if it uses the same job ID,
+         * so only call it after you've finished processing the most recent changes (in other words,
+         * call {@link JobScheduler#schedule(JobInfo)} where you would have normally called
+         * {@link JobService#jobFinished(JobParameters, boolean)}.
          * Following this pattern will ensure you do not lose any content changes: while your
          * job is running, the system will continue monitoring for content changes, and propagate
-         * any it sees over to the next job you schedule.</p>
+         * any changes it sees over to the next job you schedule, so you do not have to worry
+         * about missing new changes. <b>Scheduling the new job
+         * before or during processing will cause the current job to be stopped (as described in
+         * {@link JobScheduler#schedule(JobInfo)}), meaning the wakelock will be released for the
+         * current job and your app process may be killed since it will no longer be in a valid
+         * component lifecycle.</b>
+         * Since {@link JobScheduler#schedule(JobInfo)} stops the current job, you do not
+         * need to call {@link JobService#jobFinished(JobParameters, boolean)} if you call
+         * {@link JobScheduler#schedule(JobInfo)} using the same job ID as the
+         * currently running job.</p>
          *
          * <p>Because setting this property is not compatible with periodic or
          * persisted jobs, doing so will throw an {@link java.lang.IllegalArgumentException} when
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 14cf6b4..c706a3a 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -3343,7 +3343,7 @@
             if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
             resetLightIdleManagementLocked();
             scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
-                    mConstants.FLEX_TIME_SHORT);
+                    mConstants.FLEX_TIME_SHORT, true);
             EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
         }
     }
@@ -3424,7 +3424,7 @@
                     mLightState = LIGHT_STATE_PRE_IDLE;
                     EventLogTags.writeDeviceIdleLight(mLightState, reason);
                     scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT,
-                            mConstants.FLEX_TIME_SHORT);
+                            mConstants.FLEX_TIME_SHORT, true);
                     break;
                 }
                 // Nothing active, fall through to immediately idle.
@@ -3443,7 +3443,7 @@
                     }
                 }
                 mMaintenanceStartTime = 0;
-                scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex);
+                scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex, false);
                 mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                         (long) (mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
                 mNextLightIdleDelayFlex = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT_FLEX,
@@ -3467,7 +3467,7 @@
                     } else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
                         mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
                     }
-                    scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT);
+                    scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT, true);
                     if (DEBUG) Slog.d(TAG,
                             "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                     mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
@@ -3478,7 +3478,8 @@
                     // We'd like to do maintenance, but currently don't have network
                     // connectivity...  let's try to wait until the network comes back.
                     // We'll only wait for another full idle period, however, and then give up.
-                    scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex / 2);
+                    scheduleLightAlarmLocked(mNextLightIdleDelay,
+                            mNextLightIdleDelayFlex / 2, true);
                     if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
                     mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
                     EventLogTags.writeDeviceIdleLight(mLightState, reason);
@@ -4034,17 +4035,22 @@
     }
 
     @GuardedBy("this")
-    void scheduleLightAlarmLocked(long delay, long flex) {
+    void scheduleLightAlarmLocked(long delay, long flex, boolean wakeup) {
         if (DEBUG) {
             Slog.d(TAG, "scheduleLightAlarmLocked(" + delay
-                    + (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "") + ")");
+                    + (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "")
+                    + ", wakeup=" + wakeup + ")");
         }
         mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
         if (mConstants.USE_WINDOW_ALARMS) {
-            mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextLightAlarmTime, flex,
+            mAlarmManager.setWindow(
+                    wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME,
+                    mNextLightAlarmTime, flex,
                     "DeviceIdleController.light", mLightAlarmListener, mHandler);
         } else {
-            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextLightAlarmTime,
+            mAlarmManager.set(
+                    wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME,
+                    mNextLightAlarmTime,
                     "DeviceIdleController.light", mLightAlarmListener, mHandler);
         }
     }
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 3da508d..86d7a5a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1559,6 +1559,13 @@
         }
     }
 
+    /** Return the current bias of the given UID. */
+    public int getUidBias(int uid) {
+        synchronized (mLock) {
+            return mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
+        }
+    }
+
     @Override
     public void onDeviceIdleStateChanged(boolean deviceIdle) {
         synchronized (mLock) {
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 9cae8645..00d1bff 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -366,9 +366,6 @@
             } catch (RemoteException e) {
                 // Whatever.
             }
-            mEconomyManagerInternal.noteOngoingEventStarted(
-                    job.getSourceUserId(), job.getSourcePackageName(),
-                    getRunningActionId(job), String.valueOf(job.getJobId()));
             final String jobPackage = job.getSourcePackageName();
             final int jobUserId = job.getSourceUserId();
             UsageStatsManagerInternal usageStats =
@@ -401,25 +398,6 @@
         }
     }
 
-    @EconomicPolicy.AppAction
-    private static int getRunningActionId(@NonNull JobStatus job) {
-        switch (job.getEffectivePriority()) {
-            case JobInfo.PRIORITY_MAX:
-                return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING;
-            case JobInfo.PRIORITY_HIGH:
-                return JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING;
-            case JobInfo.PRIORITY_LOW:
-                return JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING;
-            case JobInfo.PRIORITY_MIN:
-                return JobSchedulerEconomicPolicy.ACTION_JOB_MIN_RUNNING;
-            default:
-                Slog.wtf(TAG, "Unknown priority: " + getPriorityString(job.getEffectivePriority()));
-                // Intentional fallthrough
-            case JobInfo.PRIORITY_DEFAULT:
-                return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING;
-        }
-    }
-
     /**
      * Used externally to query the running job. Will return null if there is no job running.
      */
@@ -1043,9 +1021,6 @@
         } catch (RemoteException e) {
             // Whatever.
         }
-        mEconomyManagerInternal.noteOngoingEventStopped(
-                mRunningJob.getSourceUserId(), mRunningJob.getSourcePackageName(),
-                getRunningActionId(mRunningJob), String.valueOf(mRunningJob.getJobId()));
         if (mParams.getStopReason() == JobParameters.STOP_REASON_TIMEOUT) {
             mEconomyManagerInternal.noteInstantaneousEvent(
                     mRunningJob.getSourceUserId(), mRunningJob.getSourcePackageName(),
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 be3a3ee..fd6aa7a 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
@@ -16,9 +16,12 @@
 
 package com.android.server.job.controllers;
 
+import static android.app.job.JobInfo.getPriorityString;
+
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
 import android.annotation.NonNull;
+import android.app.job.JobInfo;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
@@ -30,6 +33,7 @@
 import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
+import com.android.server.tare.EconomicPolicy;
 import com.android.server.tare.EconomyManagerInternal;
 import com.android.server.tare.EconomyManagerInternal.ActionBill;
 import com.android.server.tare.JobSchedulerEconomicPolicy;
@@ -49,6 +53,56 @@
             || Log.isLoggable(TAG, Log.DEBUG);
 
     /**
+     * Bill to use while we're waiting to start a min priority job. If a job isn't running yet,
+     * don't consider it eligible to run unless it can pay for a job start and at least some
+     * period of execution time. We don't want min priority jobs to use up all available credits,
+     * so we make sure to only run them while there are enough credits to run higher priority jobs.
+     */
+    private static final ActionBill BILL_JOB_START_MIN =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START, 1, 0),
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 120_000L)
+            ));
+
+    /**
+     * Bill to use when a min priority job is currently running. We don't want min priority jobs
+     * to use up remaining credits, so we make sure to only run them while there are enough
+     * credits to run higher priority jobs. We stop the job when the app's credits get too low.
+     */
+    private static final ActionBill BILL_JOB_RUNNING_MIN =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 60_000L)
+            ));
+
+    /**
+     * Bill to use while we're waiting to start a low priority job. If a job isn't running yet,
+     * don't consider it eligible to run unless it can pay for a job start and at least some
+     * period of execution time. We don't want low priority jobs to use up all available credits,
+     * so we make sure to only run them while there are enough credits to run higher priority jobs.
+     */
+    private static final ActionBill BILL_JOB_START_LOW =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START, 1, 0),
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 60_000L)
+            ));
+
+    /**
+     * Bill to use when a low priority job is currently running. We don't want low priority jobs
+     * to use up all available credits, so we make sure to only run them while there are enough
+     * credits to run higher priority jobs. We stop the job when the app's credits get too low.
+     */
+    private static final ActionBill BILL_JOB_RUNNING_LOW =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING, 0, 30_000L)
+            ));
+
+    /**
      * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it
      * eligible to run unless it can pay for a job start and at least some period of execution time.
      */
@@ -61,9 +115,9 @@
             ));
 
     /**
-     * Bill to use when a default is currently running. We want to track and make sure the app can
-     * continue to pay for 1 more second of execution time. We stop the job when the app can no
-     * longer pay for that time.
+     * Bill to use when a default priority job is currently running. We want to track and make
+     * sure the app can continue to pay for 1 more second of execution time. We stop the job when
+     * the app can no longer pay for that time.
      */
     private static final ActionBill BILL_JOB_RUNNING_DEFAULT =
             new ActionBill(List.of(
@@ -75,7 +129,33 @@
      * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it
      * eligible to run unless it can pay for a job start and at least some period of execution time.
      */
-    private static final ActionBill BILL_JOB_START_EXPEDITED =
+    private static final ActionBill BILL_JOB_START_HIGH =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0),
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 30_000L)
+            ));
+
+    /**
+     * Bill to use when a high priority job is currently running. We want to track and make sure
+     * the app can continue to pay for 1 more second of execution time. We stop the job when the
+     * app can no longer pay for that time.
+     */
+    private static final ActionBill BILL_JOB_RUNNING_HIGH =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 1_000L)
+            ));
+
+
+    /**
+     * Bill to use while we're waiting to start a max priority job. This should only be used for
+     * requested-EJs that aren't allowed to run as EJs. If a job isn't running yet, don't consider
+     * it eligible to run unless it can pay for a job start and at least some period of execution
+     * time.
+     */
+    private static final ActionBill BILL_JOB_START_MAX =
             new ActionBill(List.of(
                     new EconomyManagerInternal.AnticipatedAction(
                             JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0),
@@ -84,16 +164,63 @@
             ));
 
     /**
-     * Bill to use when an EJ is currently running (as an EJ). We want to track and make sure the
-     * app can continue to pay for 1 more second of execution time. We stop the job when the app can
-     * no longer pay for that time.
+     * Bill to use when a max priority job is currently running. This should only be used for
+     * requested-EJs that aren't allowed to run as EJs. We want to track and make sure
+     * the app can continue to pay for 1 more second of execution time. We stop the job when the
+     * app can no longer pay for that time.
      */
-    private static final ActionBill BILL_JOB_RUNNING_EXPEDITED =
+    private static final ActionBill BILL_JOB_RUNNING_MAX =
             new ActionBill(List.of(
                     new EconomyManagerInternal.AnticipatedAction(
                             JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1_000L)
             ));
 
+    /**
+     * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it
+     * eligible to run unless it can pay for a job start and at least some period of execution time.
+     */
+    private static final ActionBill BILL_JOB_START_MAX_EXPEDITED =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START, 1, 0),
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 30_000L)
+            ));
+
+    /**
+     * Bill to use when a max priority EJ is currently running (as an EJ). We want to track and
+     * make sure the app can continue to pay for 1 more second of execution time. We stop the job
+     * when the app can no longer pay for that time.
+     */
+    private static final ActionBill BILL_JOB_RUNNING_MAX_EXPEDITED =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING, 0, 1_000L)
+            ));
+
+    /**
+     * Bill to use while we're waiting to start a job. If a job isn't running yet, don't consider it
+     * eligible to run unless it can pay for a job start and at least some period of execution time.
+     */
+    private static final ActionBill BILL_JOB_START_HIGH_EXPEDITED =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START, 1, 0),
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 30_000L)
+            ));
+
+    /**
+     * Bill to use when a high priority EJ is currently running (as an EJ). We want to track and
+     * make sure the app can continue to pay for 1 more second of execution time. We stop the job
+     * when the app can no longer pay for that time.
+     */
+    private static final ActionBill BILL_JOB_RUNNING_HIGH_EXPEDITED =
+            new ActionBill(List.of(
+                    new EconomyManagerInternal.AnticipatedAction(
+                            JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING, 0, 1_000L)
+            ));
+
     private final EconomyManagerInternal mEconomyManagerInternal;
 
     private final BackgroundJobsController mBackgroundJobsController;
@@ -161,6 +288,14 @@
                 }
             };
 
+    /**
+     * List of jobs that started while the UID was in the TOP state. There will be no more than
+     * 16 ({@link JobSchedulerService#MAX_JOB_CONTEXTS_COUNT}) running at once, so an ArraySet is
+     * fine.
+     */
+    @GuardedBy("mLock")
+    private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();
+
     @GuardedBy("mLock")
     private boolean mIsEnabled;
 
@@ -175,6 +310,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         final long nowElapsed = sElapsedRealtimeClock.millis();
         jobStatus.setTareWealthConstraintSatisfied(nowElapsed, hasEnoughWealthLocked(jobStatus));
@@ -188,6 +324,7 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void prepareForExecutionLocked(JobStatus jobStatus) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
@@ -200,16 +337,30 @@
                 removeJobFromBillList(jobStatus, billToJobMap.keyAt(i));
             }
         }
-        if (jobStatus.shouldTreatAsExpeditedJob()) {
-            addJobToBillList(jobStatus, BILL_JOB_RUNNING_EXPEDITED);
+        addJobToBillList(jobStatus, getRunningBill(jobStatus));
+
+        final int uid = jobStatus.getSourceUid();
+        if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) {
+            if (DEBUG) {
+                Slog.d(TAG, jobStatus.toShortString() + " is top started job");
+            }
+            mTopStartedJobs.add(jobStatus);
+            // Top jobs won't count towards quota so there's no need to involve the EconomyManager.
+        } else {
+            mEconomyManagerInternal.noteOngoingEventStarted(userId, pkgName,
+                    getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
         }
-        addJobToBillList(jobStatus, BILL_JOB_RUNNING_DEFAULT);
     }
 
     @Override
+    @GuardedBy("mLock")
     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
+        mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
+                getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
+        mTopStartedJobs.remove(jobStatus);
+
         final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus);
         ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
                 mRegisteredBillsAndJobs.get(userId, pkgName);
@@ -226,10 +377,14 @@
     }
 
     @Override
+    @GuardedBy("mLock")
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
+        mEconomyManagerInternal.noteOngoingEventStopped(userId, pkgName,
+                getRunningActionId(jobStatus), String.valueOf(jobStatus.getJobId()));
+        mTopStartedJobs.remove(jobStatus);
         ArrayMap<ActionBill, ArraySet<JobStatus>> billToJobMap =
                 mRegisteredBillsAndJobs.get(userId, pkgName);
         if (billToJobMap != null) {
@@ -270,7 +425,16 @@
         if (!mIsEnabled) {
             return true;
         }
-        return canAffordBillLocked(jobStatus, BILL_JOB_START_EXPEDITED);
+        if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) {
+            return canAffordBillLocked(jobStatus, BILL_JOB_START_MAX_EXPEDITED);
+        }
+        return canAffordBillLocked(jobStatus, BILL_JOB_START_HIGH_EXPEDITED);
+    }
+
+    /** @return true if the job was started while the app was in the TOP state. */
+    @GuardedBy("mLock")
+    private boolean isTopStartedJobLocked(@NonNull final JobStatus jobStatus) {
+        return mTopStartedJobs.contains(jobStatus);
     }
 
     @GuardedBy("mLock")
@@ -278,14 +442,9 @@
         if (!mIsEnabled) {
             return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
         }
-        if (jobStatus.shouldTreatAsExpeditedJob()) {
-            return mEconomyManagerInternal.getMaxDurationMs(
-                    jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
-                    BILL_JOB_RUNNING_EXPEDITED);
-        }
         return mEconomyManagerInternal.getMaxDurationMs(
                 jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
-                BILL_JOB_RUNNING_DEFAULT);
+                getRunningBill(jobStatus));
     }
 
     @GuardedBy("mLock")
@@ -336,17 +495,93 @@
         // TODO: factor in network cost when available
         final ArraySet<ActionBill> bills = new ArraySet<>();
         if (jobStatus.isRequestedExpeditedJob()) {
-            bills.add(BILL_JOB_START_EXPEDITED);
+            if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) {
+                bills.add(BILL_JOB_START_MAX_EXPEDITED);
+            } else {
+                bills.add(BILL_JOB_START_HIGH_EXPEDITED);
+            }
         }
-        bills.add(BILL_JOB_START_DEFAULT);
+        switch (jobStatus.getEffectivePriority()) {
+            case JobInfo.PRIORITY_MAX:
+                bills.add(BILL_JOB_START_MAX);
+                break;
+            case JobInfo.PRIORITY_HIGH:
+                bills.add(BILL_JOB_START_HIGH);
+                break;
+            case JobInfo.PRIORITY_DEFAULT:
+                bills.add(BILL_JOB_START_DEFAULT);
+                break;
+            case JobInfo.PRIORITY_LOW:
+                bills.add(BILL_JOB_START_LOW);
+                break;
+            case JobInfo.PRIORITY_MIN:
+                bills.add(BILL_JOB_START_MIN);
+                break;
+            default:
+                Slog.wtf(TAG, "Unexpected priority: "
+                        + JobInfo.getPriorityString(jobStatus.getEffectivePriority()));
+                break;
+        }
         return bills;
     }
 
+    @NonNull
+    private ActionBill getRunningBill(JobStatus jobStatus) {
+        // TODO: factor in network cost when available
+        if (jobStatus.shouldTreatAsExpeditedJob()) {
+            if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) {
+                return BILL_JOB_RUNNING_MAX_EXPEDITED;
+            } else {
+                return BILL_JOB_RUNNING_HIGH_EXPEDITED;
+            }
+        }
+        switch (jobStatus.getEffectivePriority()) {
+            case JobInfo.PRIORITY_MAX:
+                return BILL_JOB_RUNNING_MAX;
+            case JobInfo.PRIORITY_HIGH:
+                return BILL_JOB_RUNNING_HIGH;
+            case JobInfo.PRIORITY_LOW:
+                return BILL_JOB_RUNNING_LOW;
+            case JobInfo.PRIORITY_MIN:
+                return BILL_JOB_RUNNING_MIN;
+            default:
+                Slog.wtf(TAG, "Got unexpected priority: " + jobStatus.getEffectivePriority());
+                // Intentional fallthrough
+            case JobInfo.PRIORITY_DEFAULT:
+                return BILL_JOB_RUNNING_DEFAULT;
+        }
+    }
+
+    @EconomicPolicy.AppAction
+    private static int getRunningActionId(@NonNull JobStatus job) {
+        switch (job.getEffectivePriority()) {
+            case JobInfo.PRIORITY_MAX:
+                return JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING;
+            case JobInfo.PRIORITY_HIGH:
+                return JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING;
+            case JobInfo.PRIORITY_LOW:
+                return JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING;
+            case JobInfo.PRIORITY_MIN:
+                return JobSchedulerEconomicPolicy.ACTION_JOB_MIN_RUNNING;
+            default:
+                Slog.wtf(TAG, "Unknown priority: " + getPriorityString(job.getEffectivePriority()));
+                // Intentional fallthrough
+            case JobInfo.PRIORITY_DEFAULT:
+                return JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING;
+        }
+    }
+
     @GuardedBy("mLock")
     private boolean canAffordBillLocked(@NonNull JobStatus jobStatus, @NonNull ActionBill bill) {
         if (!mIsEnabled) {
             return true;
         }
+        if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
+                || isTopStartedJobLocked(jobStatus)) {
+            // Jobs for the top app should always be allowed to run, and any jobs started while
+            // the app is on top shouldn't consume any credits.
+            return true;
+        }
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         ArrayMap<ActionBill, Boolean> actionAffordability =
@@ -370,11 +605,23 @@
         if (!mIsEnabled) {
             return true;
         }
+        if (!jobStatus.isRequestedExpeditedJob()) {
+            return false;
+        }
+        if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
+                || isTopStartedJobLocked(jobStatus)) {
+            // Jobs for the top app should always be allowed to run, and any jobs started while
+            // the app is on top shouldn't consume any credits.
+            return true;
+        }
         if (mService.isCurrentlyRunningLocked(jobStatus)) {
-            return canAffordBillLocked(jobStatus, BILL_JOB_RUNNING_EXPEDITED);
+            return canAffordBillLocked(jobStatus, getRunningBill(jobStatus));
         }
 
-        return canAffordBillLocked(jobStatus, BILL_JOB_START_EXPEDITED);
+        if (jobStatus.getEffectivePriority() == JobInfo.PRIORITY_MAX) {
+            return canAffordBillLocked(jobStatus, BILL_JOB_START_MAX_EXPEDITED);
+        }
+        return canAffordBillLocked(jobStatus, BILL_JOB_START_HIGH_EXPEDITED);
     }
 
     @GuardedBy("mLock")
@@ -382,20 +629,28 @@
         if (!mIsEnabled) {
             return true;
         }
-        if (mService.isCurrentlyRunningLocked(jobStatus)) {
-            if (jobStatus.isRequestedExpeditedJob()) {
-                return canAffordBillLocked(jobStatus, BILL_JOB_RUNNING_EXPEDITED)
-                        || canAffordBillLocked(jobStatus, BILL_JOB_RUNNING_DEFAULT);
-            }
-            return canAffordBillLocked(jobStatus, BILL_JOB_RUNNING_DEFAULT);
-        }
-
-        if (jobStatus.isRequestedExpeditedJob()
-                && canAffordBillLocked(jobStatus, BILL_JOB_START_EXPEDITED)) {
+        if (mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
+                || isTopStartedJobLocked(jobStatus)) {
+            // Jobs for the top app should always be allowed to run, and any jobs started while
+            // the app is on top shouldn't consume any credits.
             return true;
         }
+        if (mService.isCurrentlyRunningLocked(jobStatus)) {
+            if (jobStatus.isRequestedExpeditedJob()) {
+                return canAffordBillLocked(jobStatus, getRunningBill(jobStatus))
+                        || canAffordBillLocked(jobStatus, BILL_JOB_RUNNING_DEFAULT);
+            }
+            return canAffordBillLocked(jobStatus, getRunningBill(jobStatus));
+        }
 
-        return canAffordBillLocked(jobStatus, BILL_JOB_START_DEFAULT);
+        final ArraySet<ActionBill> bills = getPossibleStartBills(jobStatus);
+        for (int i = 0; i < bills.size(); ++i) {
+            ActionBill bill = bills.valueAt(i);
+            if (canAffordBillLocked(jobStatus, bill)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -417,11 +672,23 @@
 
     @NonNull
     private String getBillName(@NonNull ActionBill bill) {
-        if (bill.equals(BILL_JOB_START_EXPEDITED)) {
-            return "EJ_START_BILL";
+        if (bill.equals(BILL_JOB_START_MAX_EXPEDITED)) {
+            return "EJ_MAX_START_BILL";
         }
-        if (bill.equals(BILL_JOB_RUNNING_EXPEDITED)) {
-            return "EJ_RUNNING_BILL";
+        if (bill.equals(BILL_JOB_RUNNING_MAX_EXPEDITED)) {
+            return "EJ_MAX_RUNNING_BILL";
+        }
+        if (bill.equals(BILL_JOB_START_HIGH_EXPEDITED)) {
+            return "EJ_HIGH_START_BILL";
+        }
+        if (bill.equals(BILL_JOB_RUNNING_HIGH_EXPEDITED)) {
+            return "EJ_HIGH_RUNNING_BILL";
+        }
+        if (bill.equals(BILL_JOB_START_HIGH)) {
+            return "HIGH_START_BILL";
+        }
+        if (bill.equals(BILL_JOB_RUNNING_HIGH)) {
+            return "HIGH_RUNNING_BILL";
         }
         if (bill.equals(BILL_JOB_START_DEFAULT)) {
             return "DEFAULT_START_BILL";
@@ -429,7 +696,19 @@
         if (bill.equals(BILL_JOB_RUNNING_DEFAULT)) {
             return "DEFAULT_RUNNING_BILL";
         }
-        return "UNKNOWN_BILL (" + bill.toString() + ")";
+        if (bill.equals(BILL_JOB_START_LOW)) {
+            return "LOW_START_BILL";
+        }
+        if (bill.equals(BILL_JOB_RUNNING_LOW)) {
+            return "LOW_RUNNING_BILL";
+        }
+        if (bill.equals(BILL_JOB_START_MIN)) {
+            return "MIN_START_BILL";
+        }
+        if (bill.equals(BILL_JOB_RUNNING_MIN)) {
+            return "MIN_RUNNING_BILL";
+        }
+        return "UNKNOWN_BILL (" + bill + ")";
     }
 
     @Override
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 005c447..a6a007f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -46,6 +46,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArrayMap;
+import android.util.TimeUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -681,8 +682,8 @@
             final long minBalance = mIrs.getMinBalanceLocked(userId, pkgName);
             final double perc = batteryLevel / 100d;
             // TODO: maybe don't give credits to bankrupt apps until battery level >= 50%
-            if (ledger.getCurrentBalance() < minBalance) {
-                final long shortfall = minBalance - getBalanceLocked(userId, pkgName);
+            final long shortfall = minBalance - ledger.getCurrentBalance();
+            if (shortfall > 0) {
                 recordTransactionLocked(userId, pkgName, ledger,
                         new Ledger.Transaction(now, now, REGULATION_BASIC_INCOME,
                                 null, (long) (perc * shortfall)), true);
@@ -1170,5 +1171,57 @@
     void dumpLocked(IndentingPrintWriter pw) {
         pw.println();
         mBalanceThresholdAlarmQueue.dump(pw);
+
+        pw.println();
+        pw.println("Ongoing events:");
+        pw.increaseIndent();
+        boolean printedEvents = false;
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        for (int u = mCurrentOngoingEvents.numMaps() - 1; u >= 0; --u) {
+            final int userId = mCurrentOngoingEvents.keyAt(u);
+            for (int p = mCurrentOngoingEvents.numElementsForKey(userId) - 1; p >= 0; --p) {
+                final String pkgName = mCurrentOngoingEvents.keyAt(u, p);
+                final SparseArrayMap<String, OngoingEvent> ongoingEvents =
+                        mCurrentOngoingEvents.get(userId, pkgName);
+
+                boolean printedApp = false;
+
+                for (int e = ongoingEvents.numMaps() - 1; e >= 0; --e) {
+                    final int eventId = ongoingEvents.keyAt(e);
+                    for (int t = ongoingEvents.numElementsForKey(eventId) - 1; t >= 0; --t) {
+                        if (!printedApp) {
+                            printedApp = true;
+                            pw.println(appToString(userId, pkgName));
+                            pw.increaseIndent();
+                        }
+                        printedEvents = true;
+
+                        OngoingEvent ongoingEvent = ongoingEvents.valueAt(e, t);
+
+                        pw.print(EconomicPolicy.eventToString(ongoingEvent.eventId));
+                        if (ongoingEvent.tag != null) {
+                            pw.print("(");
+                            pw.print(ongoingEvent.tag);
+                            pw.print(")");
+                        }
+                        pw.print(" runtime=");
+                        TimeUtils.formatDuration(nowElapsed - ongoingEvent.startTimeElapsed, pw);
+                        pw.print(" delta/sec=");
+                        pw.print(ongoingEvent.deltaPerSec);
+                        pw.print(" refCount=");
+                        pw.print(ongoingEvent.refCount);
+                        pw.println();
+                    }
+                }
+
+                if (printedApp) {
+                    pw.decreaseIndent();
+                }
+            }
+        }
+        if (!printedEvents) {
+            pw.print("N/A");
+        }
+        pw.decreaseIndent();
     }
 }
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 20a300a..36895a5 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -567,20 +567,23 @@
         final String pkgName = event.getPackageName();
         if (DEBUG) {
             Slog.d(TAG, "Processing event " + event.getEventType()
+                    + " (" + event.mInstanceId + ")"
                     + " for " + appToString(userId, pkgName));
         }
         final long nowElapsed = SystemClock.elapsedRealtime();
         switch (event.getEventType()) {
             case UsageEvents.Event.ACTIVITY_RESUMED:
                 mAgent.noteOngoingEventLocked(userId, pkgName,
-                        EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed);
+                        EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId),
+                        nowElapsed);
                 break;
             case UsageEvents.Event.ACTIVITY_PAUSED:
             case UsageEvents.Event.ACTIVITY_STOPPED:
             case UsageEvents.Event.ACTIVITY_DESTROYED:
                 final long now = getCurrentTimeMillis();
                 mAgent.stopOngoingActionLocked(userId, pkgName,
-                        EconomicPolicy.REWARD_TOP_ACTIVITY, null, nowElapsed, now);
+                        EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId),
+                        nowElapsed, now);
                 break;
             case UsageEvents.Event.USER_INTERACTION:
             case UsageEvents.Event.CHOOSER_ACTION:
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index a234ae6..f4917ad 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -138,6 +138,11 @@
             dumpTime(pw, transaction.endTimeMs);
             pw.print(": ");
             pw.print(EconomicPolicy.eventToString(transaction.eventId));
+            if (transaction.tag != null) {
+                pw.print("(");
+                pw.print(transaction.tag);
+                pw.print(")");
+            }
             pw.print(" --> ");
             pw.println(narcToString(transaction.delta));
         }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index af4053f..05a06619 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -105,6 +105,7 @@
 static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
 static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress";
 static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
+static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
 static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
 static constexpr size_t TEXT_POS_LEN_MAX = 16;
 static const int DYNAMIC_COLOR_COUNT = 4;
@@ -1320,6 +1321,8 @@
     }
     if (!anyPartHasClock) {
         mClockEnabled = false;
+    } else if (!android::base::GetBoolProperty(CLOCK_ENABLED_PROP_NAME, false)) {
+        mClockEnabled = false;
     }
 
     // Check if npot textures are supported
diff --git a/core/api/current.txt b/core/api/current.txt
index fd429f0..f84322e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -123,6 +123,7 @@
     field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
     field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
     field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+    field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
@@ -1419,6 +1420,7 @@
     field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
     field public static final int supportsRtl = 16843695; // 0x10103af
+    field public static final int supportsStylusHandwriting;
     field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
     field public static final int supportsUploading = 16843419; // 0x101029b
     field public static final int suppressesSpellChecker = 16844355; // 0x1010643
@@ -2044,6 +2046,7 @@
     field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
     field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
     field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
+    field public static final int accessibilityActionShowSuggestions;
     field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
     field public static final int accessibilityActionSwipeDown;
     field public static final int accessibilityActionSwipeLeft;
@@ -3121,11 +3124,13 @@
     method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, @Nullable android.os.Handler);
     method public float getCenterX();
     method public float getCenterY();
+    method @Nullable public android.accessibilityservice.MagnificationConfig getMagnificationConfig();
     method @NonNull public android.graphics.Region getMagnificationRegion();
     method public float getScale();
     method public boolean removeListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
     method public boolean reset(boolean);
     method public boolean setCenter(float, float, boolean);
+    method public boolean setMagnificationConfig(@NonNull android.accessibilityservice.MagnificationConfig, boolean);
     method public boolean setScale(float, boolean);
   }
 
@@ -3144,8 +3149,12 @@
     method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, @Nullable android.os.Handler);
     method public int getShowMode();
     method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+    method @CheckResult public int setInputMethodEnabled(@NonNull String, boolean) throws java.lang.SecurityException;
     method public boolean setShowMode(int);
     method public boolean switchToInputMethod(@NonNull String);
+    field public static final int ENABLE_IME_FAIL_BY_ADMIN = 1; // 0x1
+    field public static final int ENABLE_IME_FAIL_UNKNOWN = 2; // 0x2
+    field public static final int ENABLE_IME_SUCCESS = 0; // 0x0
   }
 
   public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -5439,12 +5448,30 @@
 
   public final class GameManager {
     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_PERFORMANCE = 2; // 0x2
     field public static final int GAME_MODE_STANDARD = 1; // 0x1
     field public static final int GAME_MODE_UNSUPPORTED = 0; // 0x0
   }
 
+  public final class GameState implements android.os.Parcelable {
+    ctor public GameState(boolean, int);
+    ctor public GameState(boolean, int, @Nullable String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @Nullable public String getDescription();
+    method @NonNull public android.os.Bundle getMetadata();
+    method public int getMode();
+    method public boolean isLoading();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.GameState> CREATOR;
+    field public static final int MODE_CONTENT = 4; // 0x4
+    field public static final int MODE_GAMEPLAY_INTERRUPTIBLE = 2; // 0x2
+    field public static final int MODE_GAMEPLAY_UNINTERRUPTIBLE = 3; // 0x3
+    field public static final int MODE_NONE = 1; // 0x1
+    field public static final int MODE_UNKNOWN = 0; // 0x0
+  }
+
   public class Instrumentation {
     ctor public Instrumentation();
     method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -6745,6 +6772,7 @@
     field public static final int START_STICKY = 1; // 0x1
     field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0
     field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2
+    field @Deprecated public static final int STOP_FOREGROUND_LEGACY = 0; // 0x0
     field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1
   }
 
@@ -8752,10 +8780,10 @@
     method public android.bluetooth.BluetoothDevice getRemoteDevice(byte[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getScanMode();
     method public int getState();
-    method public int isCisCentralSupported();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
     method public boolean isEnabled();
     method public boolean isLe2MPhySupported();
+    method public int isLeAudioSupported();
     method public boolean isLeCodedPhySupported();
     method public boolean isLeExtendedAdvertisingSupported();
     method public boolean isLePeriodicAdvertisingSupported();
@@ -9131,6 +9159,72 @@
     field public static final int TELEPHONY = 4194304; // 0x400000
   }
 
+  public final class BluetoothCodecConfig implements android.os.Parcelable {
+    ctor public BluetoothCodecConfig(int);
+    method public int describeContents();
+    method public int getBitsPerSample();
+    method public int getChannelMode();
+    method public int getCodecPriority();
+    method public long getCodecSpecific1();
+    method public long getCodecSpecific2();
+    method public long getCodecSpecific3();
+    method public long getCodecSpecific4();
+    method public int getCodecType();
+    method public static int getMaxCodecType();
+    method public int getSampleRate();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
+    field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
+    field public static final int BITS_PER_SAMPLE_32 = 4; // 0x4
+    field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_MONO = 1; // 0x1
+    field public static final int CHANNEL_MODE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
+    field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
+    field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
+    field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecConfig> CREATOR;
+    field public static final int SAMPLE_RATE_176400 = 16; // 0x10
+    field public static final int SAMPLE_RATE_192000 = 32; // 0x20
+    field public static final int SAMPLE_RATE_44100 = 1; // 0x1
+    field public static final int SAMPLE_RATE_48000 = 2; // 0x2
+    field public static final int SAMPLE_RATE_88200 = 4; // 0x4
+    field public static final int SAMPLE_RATE_96000 = 8; // 0x8
+    field public static final int SAMPLE_RATE_NONE = 0; // 0x0
+    field public static final int SOURCE_CODEC_TYPE_AAC = 1; // 0x1
+    field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2
+    field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3
+    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+    field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4
+    field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0
+  }
+
+  public static final class BluetoothCodecConfig.Builder {
+    ctor public BluetoothCodecConfig.Builder();
+    method @NonNull public android.bluetooth.BluetoothCodecConfig build();
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setBitsPerSample(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setChannelMode(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecPriority(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific1(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific2(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific3(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific4(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecType(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setSampleRate(int);
+  }
+
+  public final class BluetoothCodecStatus implements android.os.Parcelable {
+    ctor public BluetoothCodecStatus(@Nullable android.bluetooth.BluetoothCodecConfig, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>);
+    method public int describeContents();
+    method @Nullable public android.bluetooth.BluetoothCodecConfig getCodecConfig();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsLocalCapabilities();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsSelectableCapabilities();
+    method public boolean isCodecConfigSelectable(@Nullable android.bluetooth.BluetoothCodecConfig);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecStatus> CREATOR;
+    field public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS";
+  }
+
   public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
     method public void close();
     method protected void finalize();
@@ -9219,10 +9313,10 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public android.bluetooth.BluetoothDevice getDevice();
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
@@ -10056,8 +10150,10 @@
     ctor public CompanionDeviceService();
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]);
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @MainThread public abstract void onDeviceAppeared(@NonNull String);
-    method @MainThread public abstract void onDeviceDisappeared(@NonNull String);
+    method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
+    method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+    method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
+    method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
     method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]);
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
@@ -10154,6 +10250,7 @@
     method @Nullable public String getPackageName();
     method public int getUid();
     method public boolean isTrusted(@NonNull android.content.Context);
+    method @NonNull public static android.content.AttributionSource myAttributionSource();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
   }
@@ -11372,6 +11469,7 @@
     field public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE = "android.intent.action.VIEW_PERMISSION_USAGE";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD = "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
+    field @RequiresPermission("android.permission.MANAGE_SENSOR_PRIVACY") public static final String ACTION_VIEW_SAFETY_HUB = "android.intent.action.VIEW_SAFETY_HUB";
     field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -11470,6 +11568,7 @@
     field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
     field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+    field public static final String EXTRA_PREVIOUS_UID = "android.intent.extra.PREVIOUS_UID";
     field public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
     field public static final String EXTRA_QUICK_VIEW_FEATURES = "android.intent.extra.QUICK_VIEW_FEATURES";
@@ -11501,6 +11600,7 @@
     field public static final String EXTRA_TIMEZONE = "time-zone";
     field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final String EXTRA_UID = "android.intent.extra.UID";
+    field public static final String EXTRA_UID_CHANGING = "android.intent.extra.UID_CHANGING";
     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 int FILL_IN_ACTION = 1; // 0x1
@@ -12596,7 +12696,7 @@
     method @NonNull public java.io.OutputStream openWrite(@NonNull String, long, long) throws java.io.IOException;
     method public void removeChildSessionId(int);
     method public void removeSplit(@NonNull String) throws java.io.IOException;
-    method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
+    method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
     method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
     method public void setStagingProgress(float);
     method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12732,7 +12832,8 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
@@ -12741,7 +12842,8 @@
     method public abstract int getApplicationEnabledSetting(@NonNull String);
     method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull android.content.pm.ApplicationInfo);
     method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12752,9 +12854,11 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
     method public void getGroupOfPlatformPermission(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.String>);
     method @NonNull public android.content.pm.InstallSourceInfo getInstallSourceInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+    method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags);
     method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int);
-    method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method @NonNull public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String);
     method @NonNull public abstract byte[] getInstantAppCookie();
     method public abstract int getInstantAppCookieMaxBytes();
@@ -12765,15 +12869,21 @@
     method @NonNull public java.util.Set<java.lang.String> getMimeGroup(@NonNull String);
     method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract String getNameForUid(int);
-    method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+    method @Deprecated @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+    method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method public abstract int[] getPackageGids(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Nullable public int[] getPackageGids(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.pm.PackageInstaller getPackageInstaller();
-    method public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract String[] getPackagesForUid(int);
-    method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+    method @NonNull public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @NonNull public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public void getPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
@@ -12781,14 +12891,18 @@
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
     method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+    method @Deprecated @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+    method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Nullable public android.os.Bundle getSuspendedPackageAppExtras();
     method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String);
     method @NonNull public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
@@ -12816,13 +12930,19 @@
     method public abstract boolean isSafeMode();
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+    method @NonNull public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
     method @NonNull public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(@NonNull String, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable java.util.List<android.content.Intent>, @NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
     method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@Nullable String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryProviderProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryReceiverProperty(@NonNull String);
@@ -12831,9 +12951,12 @@
     method public abstract void removePermission(@NonNull String);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
     method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
-    method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
-    method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
-    method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+    method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
+    method @Nullable public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
+    method @Nullable public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
+    method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+    method @Nullable public android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
     method public abstract void setApplicationCategoryHint(@NonNull String, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
     method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean setAutoRevokeWhitelisted(@NonNull String, boolean);
@@ -13035,6 +13158,11 @@
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
   }
 
+  public static final class PackageManager.ApplicationInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.ApplicationInfoFlags of(long);
+  }
+
   public static final class PackageManager.ComponentEnabledSetting implements android.os.Parcelable {
     ctor public PackageManager.ComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
     method public int describeContents();
@@ -13045,6 +13173,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.ComponentEnabledSetting> CREATOR;
   }
 
+  public static final class PackageManager.ComponentInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.ComponentInfoFlags of(long);
+  }
+
   public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
     ctor public PackageManager.NameNotFoundException();
     ctor public PackageManager.NameNotFoundException(String);
@@ -13054,6 +13187,11 @@
     method public void onChecksumsReady(@NonNull java.util.List<android.content.pm.ApkChecksum>);
   }
 
+  public static final class PackageManager.PackageInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.PackageInfoFlags of(long);
+  }
+
   public static final class PackageManager.Property implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getBoolean();
@@ -13073,6 +13211,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.Property> CREATOR;
   }
 
+  public static final class PackageManager.ResolveInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.ResolveInfoFlags of(long);
+  }
+
   @Deprecated public class PackageStats implements android.os.Parcelable {
     ctor @Deprecated public PackageStats(String);
     ctor @Deprecated public PackageStats(android.os.Parcel);
@@ -13268,6 +13411,7 @@
     method public boolean isDynamic();
     method public boolean isEnabled();
     method public boolean isImmutable();
+    method public boolean isIncludedIn(int);
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
@@ -13280,6 +13424,7 @@
     field public static final int DISABLED_REASON_UNKNOWN = 3; // 0x3
     field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
     field public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+    field public static final int SURFACE_LAUNCHER = 1; // 0x1
   }
 
   public static class ShortcutInfo.Builder {
@@ -13288,6 +13433,7 @@
     method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setDisabledMessage(@NonNull CharSequence);
+    method @NonNull public android.content.pm.ShortcutInfo.Builder setExcludedFromSurfaces(int);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setIntent(@NonNull android.content.Intent);
@@ -14304,11 +14450,21 @@
     field public static final int CONFLICT_ROLLBACK = 1; // 0x1
     field public static final int CREATE_IF_NECESSARY = 268435456; // 0x10000000
     field public static final int ENABLE_WRITE_AHEAD_LOGGING = 536870912; // 0x20000000
+    field public static final String JOURNAL_MODE_DELETE = "DELETE";
+    field public static final String JOURNAL_MODE_MEMORY = "MEMORY";
+    field public static final String JOURNAL_MODE_OFF = "OFF";
+    field public static final String JOURNAL_MODE_PERSIST = "PERSIST";
+    field public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
+    field public static final String JOURNAL_MODE_WAL = "WAL";
     field public static final int MAX_SQL_CACHE_SIZE = 100; // 0x64
     field public static final int NO_LOCALIZED_COLLATORS = 16; // 0x10
     field public static final int OPEN_READONLY = 1; // 0x1
     field public static final int OPEN_READWRITE = 0; // 0x0
     field public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; // 0xc350
+    field public static final String SYNC_MODE_EXTRA = "EXTRA";
+    field public static final String SYNC_MODE_FULL = "FULL";
+    field public static final String SYNC_MODE_NORMAL = "NORMAL";
+    field public static final String SYNC_MODE_OFF = "OFF";
   }
 
   public static interface SQLiteDatabase.CursorFactory {
@@ -16437,6 +16593,26 @@
     method public boolean setUseCompositingLayer(boolean, @Nullable android.graphics.Paint);
   }
 
+  public class RuntimeShader extends android.graphics.Shader {
+    ctor public RuntimeShader(@NonNull String);
+    ctor public RuntimeShader(@NonNull String, boolean);
+    method public boolean isForceOpaque();
+    method public void setColorUniform(@NonNull String, @ColorInt int);
+    method public void setColorUniform(@NonNull String, @ColorLong long);
+    method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+    method public void setFloatUniform(@NonNull String, float);
+    method public void setFloatUniform(@NonNull String, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float, float);
+    method public void setFloatUniform(@NonNull String, @NonNull float[]);
+    method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method public void setIntUniform(@NonNull String, int);
+    method public void setIntUniform(@NonNull String, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int, int);
+    method public void setIntUniform(@NonNull String, @NonNull int[]);
+  }
+
   public class Shader {
     ctor @Deprecated public Shader();
     method public boolean getLocalMatrix(@NonNull android.graphics.Matrix);
@@ -17761,6 +17937,7 @@
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
     field public static final int S_UI8 = 53; // 0x35
+    field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
     field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L
     field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
     field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
@@ -18400,6 +18577,7 @@
     method @NonNull public android.hardware.camera2.CameraExtensionCharacteristics getCameraExtensionCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
+    method public int getTorchStrengthLevel(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
@@ -18408,6 +18586,7 @@
     method public void registerTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback, @Nullable android.os.Handler);
     method public void registerTorchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraManager.TorchCallback);
     method public void setTorchMode(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
+    method public void turnOnTorchWithStrengthLevel(@NonNull String, int) throws android.hardware.camera2.CameraAccessException;
     method public void unregisterAvailabilityCallback(@NonNull android.hardware.camera2.CameraManager.AvailabilityCallback);
     method public void unregisterTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback);
   }
@@ -18425,6 +18604,7 @@
     ctor public CameraManager.TorchCallback();
     method public void onTorchModeChanged(@NonNull String, boolean);
     method public void onTorchModeUnavailable(@NonNull String);
+    method public void onTorchStrengthLevelChanged(@NonNull String, int);
   }
 
   public abstract class CameraMetadata<TKey> {
@@ -22767,7 +22947,7 @@
     method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
     method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
     method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
-    method public void setMediaCas(@NonNull android.media.MediaCas);
+    method @Deprecated public void setMediaCas(@NonNull android.media.MediaCas);
     method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
     field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
@@ -25970,11 +26150,14 @@
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
     field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
     field public static final String COLUMN_REVIEW_RATING = "review_rating";
     field public static final String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
+    field public static final String COLUMN_SCRAMBLED = "scrambled";
     field public static final String COLUMN_SEARCHABLE = "searchable";
     field public static final String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
     field @Deprecated public static final String COLUMN_SEASON_NUMBER = "season_number";
@@ -26034,7 +26217,9 @@
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
     field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes";
     field public static final String COLUMN_RECORDING_DATA_URI = "recording_data_uri";
@@ -31813,6 +31998,7 @@
     method public int dataPosition();
     method public int dataSize();
     method public void enforceInterface(@NonNull String);
+    method public void enforceNoDataAvail();
     method public boolean hasFileDescriptors();
     method public boolean hasFileDescriptors(int, int);
     method public byte[] marshall();
@@ -31824,6 +32010,7 @@
     method @Nullable public <T> java.util.ArrayList<T> readArrayList(@Nullable ClassLoader, @NonNull Class<? extends T>);
     method public void readBinderArray(@NonNull android.os.IBinder[]);
     method public void readBinderList(@NonNull java.util.List<android.os.IBinder>);
+    method @Nullable public byte[] readBlob();
     method public boolean readBoolean();
     method public void readBooleanArray(@NonNull boolean[]);
     method @Nullable public android.os.Bundle readBundle();
@@ -31851,8 +32038,8 @@
     method @Deprecated public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
     method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>);
     method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
-    method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
-    method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+    method @Nullable public <T> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
     method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
     method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
@@ -31861,7 +32048,7 @@
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
     method @Deprecated @Nullable public java.io.Serializable readSerializable();
-    method @Nullable public <T extends java.io.Serializable> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Nullable public <T> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>);
     method @NonNull public android.util.Size readSize();
     method @NonNull public android.util.SizeF readSizeF();
     method @Deprecated @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
@@ -31883,6 +32070,8 @@
     method public void writeArray(@Nullable Object[]);
     method public void writeBinderArray(@Nullable android.os.IBinder[]);
     method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>);
+    method public void writeBlob(@Nullable byte[]);
+    method public void writeBlob(@Nullable byte[], int, int);
     method public void writeBoolean(boolean);
     method public void writeBooleanArray(@Nullable boolean[]);
     method public void writeBundle(@Nullable android.os.Bundle);
@@ -32394,7 +32583,7 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
     method public long getUserCreationTime(android.os.UserHandle);
     method public android.os.UserHandle getUserForSerialNumber(long);
-    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, "android.permission.CREATE_USERS"}, conditional=true) public String getUserName();
+    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
     method public java.util.List<android.os.UserHandle> getUserProfiles();
     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);
@@ -32420,6 +32609,7 @@
     field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final String DISALLOW_ADD_USER = "no_add_user";
+    field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
     field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
     field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
@@ -32475,6 +32665,7 @@
     field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
     field public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
     field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+    field public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
     field public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
     field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
     field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
@@ -37732,6 +37923,28 @@
     method @Deprecated @NonNull public android.security.KeyPairGeneratorSpec.Builder setSubject(@NonNull javax.security.auth.x500.X500Principal);
   }
 
+  public class KeyStoreException extends java.lang.Exception {
+    method public int getNumericErrorCode();
+    method public boolean isSystemError();
+    method public boolean isTransientFailure();
+    method public boolean requiresUserAuthentication();
+    field public static final int ERROR_ATTESTATION_CHALLENGE_TOO_LARGE = 9; // 0x9
+    field public static final int ERROR_ID_ATTESTATION_FAILURE = 8; // 0x8
+    field public static final int ERROR_INCORRECT_USAGE = 13; // 0xd
+    field public static final int ERROR_INTERNAL_SYSTEM_ERROR = 4; // 0x4
+    field public static final int ERROR_KEYMINT_FAILURE = 10; // 0xa
+    field public static final int ERROR_KEYSTORE_FAILURE = 11; // 0xb
+    field public static final int ERROR_KEYSTORE_UNINITIALIZED = 3; // 0x3
+    field public static final int ERROR_KEY_CORRUPTED = 7; // 0x7
+    field public static final int ERROR_KEY_DOES_NOT_EXIST = 6; // 0x6
+    field public static final int ERROR_KEY_NOT_TEMPORALLY_VALID = 14; // 0xe
+    field public static final int ERROR_KEY_OPERATION_EXPIRED = 15; // 0xf
+    field public static final int ERROR_OTHER = 1; // 0x1
+    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
+  }
+
   @Deprecated public final class KeyStoreParameter implements java.security.KeyStore.ProtectionParameter {
     method @Deprecated public boolean isEncryptionRequired();
   }
@@ -38566,9 +38779,11 @@
 
   public abstract class CarrierService extends android.app.Service {
     ctor public CarrierService();
-    method public final void notifyCarrierNetworkChange(boolean);
+    method @Deprecated public final void notifyCarrierNetworkChange(boolean);
+    method public final void notifyCarrierNetworkChange(int, boolean);
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+    method @Deprecated public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+    method @Nullable public android.os.PersistableBundle onLoadConfig(int, @Nullable android.service.carrier.CarrierIdentifier);
     field public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
   }
 
@@ -40370,6 +40585,7 @@
     field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
     field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final String EXTRA_LAST_KNOWN_CELL_IDENTITY = "android.telecom.extra.LAST_KNOWN_CELL_IDENTITY";
     field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
     field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_CROSS_SIM = 8192; // 0x2000
@@ -41262,6 +41478,7 @@
     field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
     field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+    field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
     field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
@@ -42878,6 +43095,7 @@
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
     method public int getSubscriptionType();
+    method public int getUsageSetting();
     method public boolean isEmbedded();
     method public boolean isOpportunistic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -42907,8 +43125,8 @@
     method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int);
     method public int getDeviceToDeviceStatusSharingPreference(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public String getPhoneNumber(int, int);
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public String getPhoneNumber(int);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
     method public static int getSlotIndex(int);
     method @Nullable public int[] getSubscriptionIds(int);
     method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
@@ -42920,7 +43138,7 @@
     method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
-    method public void setCarrierPhoneNumber(int, @NonNull String);
+    method @RequiresPermission("carrier privileges") public void setCarrierPhoneNumber(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(int, @NonNull java.util.List<android.net.Uri>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
@@ -42952,6 +43170,10 @@
     field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
     field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
     field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+    field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
+    field public static final int USAGE_SETTING_DEFAULT = 0; // 0x0
+    field public static final int USAGE_SETTING_UNKNOWN = -1; // 0xffffffff
+    field public static final int USAGE_SETTING_VOICE_CENTRIC = 1; // 0x1
   }
 
   public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -43115,11 +43337,11 @@
     method public int getCarrierIdFromSimMccMnc();
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getDataNetworkType();
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getDataNetworkType();
     method public int getDataState();
     method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDeviceId();
     method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDeviceId(int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public String getDeviceSoftwareVersion();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
@@ -43151,6 +43373,7 @@
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState(boolean, boolean);
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -43172,22 +43395,22 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVisualVoicemailPackageName();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getVoiceNetworkType();
     method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
-    method @Deprecated public boolean iccCloseLogicalChannel(int);
-    method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+    method public boolean iccCloseLogicalChannel(int);
+    method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
     method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
-    method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
-    method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
-    method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+    method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+    method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
     method public boolean isDataCapable();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabledForReason(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataConnectionAllowed();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
@@ -43203,10 +43426,12 @@
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
     method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
+    method public void registerTelephonyCallback(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(boolean, @NonNull android.telephony.NetworkScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(String);
-    method @Deprecated public String sendEnvelopeWithStatus(String);
+    method public String sendEnvelopeWithStatus(String);
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(int);
@@ -43278,6 +43503,7 @@
     field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final int DATA_UNKNOWN = -1; // 0xffffffff
+    field public static final int DEFAULT_PORT_INDEX = 0; // 0x0
     field public static final int ERI_FLASH = 2; // 0x2
     field public static final int ERI_OFF = 1; // 0x1
     field public static final int ERI_ON = 0; // 0x0
@@ -44605,6 +44831,7 @@
     field public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 4096; // 0x1000
     field public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 16384; // 0x4000
     field public static final int TYPE_TEXT_FLAG_CAP_WORDS = 8192; // 0x2000
+    field public static final int TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 1048576; // 0x100000
     field public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 262144; // 0x40000
     field public static final int TYPE_TEXT_FLAG_MULTI_LINE = 131072; // 0x20000
     field public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288; // 0x80000
@@ -45734,8 +45961,10 @@
 
   public class StyleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
     ctor public StyleSpan(int);
+    ctor public StyleSpan(int, int);
     ctor public StyleSpan(@NonNull android.os.Parcel);
     method public int describeContents();
+    method public int getFontWeightAdjustment();
     method public int getSpanTypeId();
     method public int getStyle();
     method public void updateDrawState(android.text.TextPaint);
@@ -45753,6 +45982,17 @@
     method public void writeToParcel(android.os.Parcel, int);
   }
 
+  public final class SuggestionRangeSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan {
+    ctor public SuggestionRangeSpan();
+    method public int describeContents();
+    method public int getBackgroundColor();
+    method public int getSpanTypeId();
+    method public void setBackgroundColor(int);
+    method public void updateDrawState(@NonNull android.text.TextPaint);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionRangeSpan> CREATOR;
+  }
+
   public class SuggestionSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan {
     ctor public SuggestionSpan(android.content.Context, String[], int);
     ctor public SuggestionSpan(java.util.Locale, String[], int);
@@ -48898,6 +49138,7 @@
     method @NonNull public android.view.SurfaceControl build();
     method @NonNull public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
     method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+    method @NonNull public android.view.SurfaceControl.Builder setHidden(boolean);
     method @NonNull public android.view.SurfaceControl.Builder setName(@NonNull String);
     method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
     method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
@@ -48912,11 +49153,18 @@
     method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
     method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
     method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+    method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer);
     method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int);
+    method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
+    method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
     method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
+    method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
+    method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
@@ -51247,6 +51495,7 @@
     method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
     method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
+    method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
     method @ColorInt public int getAccessibilityFocusColor();
@@ -51263,6 +51512,7 @@
     method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
     method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
     method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -51278,6 +51528,10 @@
     method public void onAccessibilityStateChanged(boolean);
   }
 
+  public static interface AccessibilityManager.AudioDescriptionRequestedChangeListener {
+    method public void onAudioDescriptionRequestedChanged(boolean);
+  }
+
   public static interface AccessibilityManager.TouchExplorationStateChangeListener {
     method public void onTouchExplorationStateChanged(boolean);
   }
@@ -51526,6 +51780,7 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_SUGGESTIONS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
     field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_DOWN;
     field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_LEFT;
@@ -51551,7 +51806,6 @@
   public static final class AccessibilityNodeInfo.CollectionItemInfo {
     ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean);
     ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean);
-    ctor public AccessibilityNodeInfo.CollectionItemInfo(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
     method public int getColumnIndex();
     method public int getColumnSpan();
     method @Nullable public String getColumnTitle();
@@ -51565,6 +51819,19 @@
     method @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
   }
 
+  public static final class AccessibilityNodeInfo.CollectionItemInfo.Builder {
+    ctor public AccessibilityNodeInfo.CollectionItemInfo.Builder();
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo build();
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnIndex(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnSpan(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnTitle(@Nullable String);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setHeading(boolean);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowIndex(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowSpan(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowTitle(@Nullable String);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setSelected(boolean);
+  }
+
   public static final class AccessibilityNodeInfo.ExtraRenderingInfo {
     method @Nullable public android.util.Size getLayoutSize();
     method public float getTextSizeInPx();
@@ -52600,6 +52867,7 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public CharSequence loadLabel(android.content.pm.PackageManager);
     method public boolean shouldShowInInputMethodPicker();
+    method public boolean supportsStylusHandwriting();
     method public boolean suppressesSpellChecker();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 70bb13a..a51d2b1 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -78,6 +78,10 @@
     method @NonNull public static android.net.Uri createContentUriForUser(@NonNull android.net.Uri, @NonNull android.os.UserHandle);
   }
 
+  public abstract class ContentResolver {
+    method @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public final void registerContentObserverAsUser(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver, @NonNull android.os.UserHandle);
+  }
+
   public abstract class Context {
     method @NonNull public android.os.UserHandle getUser();
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
@@ -211,6 +215,10 @@
 
 package android.net {
 
+  public final class ConnectivityFrameworkInitializerTiramisu {
+    method public static void registerServiceWrappers();
+  }
+
   public final class EthernetNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     ctor public EthernetNetworkSpecifier(@NonNull String);
     method public int describeContents();
@@ -341,6 +349,7 @@
 package android.os.storage {
 
   public class StorageManager {
+    method public long computeStorageCacheBytes(@NonNull java.io.File);
     method public void notifyAppIoBlocked(@NonNull java.util.UUID, int, int, int);
     method public void notifyAppIoResumed(@NonNull java.util.UUID, int, int, int);
     field public static final int APP_IO_BLOCKED_REASON_TRANSCODING = 1; // 0x1
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c4aec0e..99e47b1 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,11 +70,13 @@
     field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
     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 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";
     field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
     field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
     field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
+    field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION";
     field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
@@ -218,11 +220,13 @@
     field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
     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 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";
     field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES";
     field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
+    field public static final String READ_COMMUNAL_STATE = "android.permission.READ_COMMUNAL_STATE";
     field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
     field public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
     field public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
@@ -307,6 +311,7 @@
     field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
     field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
     field public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+    field public static final String UPDATE_DEVICE_MANAGEMENT_RESOURCES = "android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES";
     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";
@@ -355,6 +360,7 @@
     field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
     field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
     field public static final int config_showDefaultHome = 17891331; // 0x1110003
+    field public static final int config_systemCaptionsServiceCallsEnabled;
   }
 
   public static final class R.color {
@@ -392,6 +398,7 @@
     field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
     field public static final int config_systemActivityRecognizer = 17039416; // 0x1040038
     field public static final int config_systemAmbientAudioIntelligence = 17039411; // 0x1040033
+    field public static final int config_systemAppProtectionService;
     field public static final int config_systemAudioIntelligence = 17039412; // 0x1040034
     field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
     field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
@@ -720,6 +727,8 @@
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
     method public void setDontSendToRestrictedApps(boolean);
     method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method public void setRequireAllOfPermissions(@Nullable String[]);
+    method public void setRequireNoneOfPermissions(@Nullable String[]);
     method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
     method public android.os.Bundle toBundle();
@@ -965,6 +974,8 @@
   }
 
   public class DevicePolicyManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int checkProvisioningPreCondition(@NonNull String, @NonNull String);
+    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.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
@@ -986,10 +997,12 @@
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean packageHasActiveAdmins(String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
     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_USERS) public void setDeviceProvisioningConfigApplied();
     method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
     method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
     field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
     field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
@@ -997,8 +1010,27 @@
     field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
     field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
     field @RequiresPermission(android.Manifest.permission.MANAGE_FACTORY_RESET_PROTECTION) public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION = "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+    field public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE = "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
     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 public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+    field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
+    field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
+    field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
+    field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
+    field public static final int CODE_HAS_PAIRED = 8; // 0x8
+    field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
+    field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
+    field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
+    field public static final int CODE_OK = 0; // 0x0
+    field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
+    field public static final int CODE_SYSTEM_USER = 10; // 0xa
+    field public static final int CODE_UNKNOWN_ERROR = -1; // 0xffffffff
+    field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
+    field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
+    field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
     field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
     field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -1021,6 +1053,10 @@
     field public static final String REQUIRED_APP_MANAGED_DEVICE = "android.app.REQUIRED_APP_MANAGED_DEVICE";
     field public static final String REQUIRED_APP_MANAGED_PROFILE = "android.app.REQUIRED_APP_MANAGED_PROFILE";
     field public static final String REQUIRED_APP_MANAGED_USER = "android.app.REQUIRED_APP_MANAGED_USER";
+    field public static final int RESULT_DEVICE_OWNER_SET = 123; // 0x7b
+    field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1; // 0x1
+    field public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2; // 0x2
+    field public static final int RESULT_WORK_PROFILE_CREATED = 122; // 0x7a
     field public static final int STATE_USER_PROFILE_COMPLETE = 4; // 0x4
     field public static final int STATE_USER_PROFILE_FINALIZED = 5; // 0x5
     field public static final int STATE_USER_SETUP_COMPLETE = 2; // 0x2
@@ -1029,6 +1065,65 @@
     field public static final int STATE_USER_UNMANAGED = 0; // 0x0
   }
 
+  public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
+    method public boolean canDeviceOwnerGrantSensorsPermissions();
+    method public int describeContents();
+    method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
+    method public long getLocalTime();
+    method @Nullable public java.util.Locale getLocale();
+    method @NonNull public String getOwnerName();
+    method @Nullable public String getTimeZone();
+    method public boolean isLeaveAllSystemAppsEnabled();
+    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
+  }
+
+  public static final class FullyManagedDeviceProvisioningParams.Builder {
+    ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setCanDeviceOwnerGrantSensorsPermissions(boolean);
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocale(@Nullable java.util.Locale);
+    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String);
+  }
+
+  public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.accounts.Account getAccountToMigrate();
+    method @NonNull public String getOwnerName();
+    method @NonNull public android.content.ComponentName getProfileAdminComponentName();
+    method @Nullable public String getProfileName();
+    method public boolean isKeepingAccountOnMigration();
+    method public boolean isLeaveAllSystemAppsEnabled();
+    method public boolean isOrganizationOwnedProvisioning();
+    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
+  }
+
+  public static final class ManagedProfileProvisioningParams.Builder {
+    ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepingAccountOnMigration(boolean);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
+    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String);
+  }
+
+  public class ProvisioningException extends android.util.AndroidException {
+    ctor public ProvisioningException(@NonNull Exception, int);
+    method public int getProvisioningError();
+    field public static final int ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED = 3; // 0x3
+    field public static final int ERROR_PRE_CONDITION_FAILED = 1; // 0x1
+    field public static final int ERROR_PROFILE_CREATION_FAILED = 2; // 0x2
+    field public static final int ERROR_REMOVE_NON_REQUIRED_APPS_FAILED = 6; // 0x6
+    field public static final int ERROR_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
+    field public static final int ERROR_SET_DEVICE_OWNER_FAILED = 7; // 0x7
+    field public static final int ERROR_STARTING_PROFILE_FAILED = 5; // 0x5
+    field public static final int ERROR_UNKNOWN = 0; // 0x0
+  }
+
   public final class SystemUpdatePolicy implements android.os.Parcelable {
     method public android.app.admin.SystemUpdatePolicy.InstallationOption getInstallationOptionAt(long);
     field public static final int TYPE_PAUSE = 4; // 0x4
@@ -1283,6 +1378,20 @@
 
 }
 
+package android.app.communal {
+
+  public final class CommunalManager {
+    method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public void addCommunalModeListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.communal.CommunalManager.CommunalModeListener);
+    method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public boolean isCommunalMode();
+    method @RequiresPermission(android.Manifest.permission.READ_COMMUNAL_STATE) public void removeCommunalModeListener(@NonNull android.app.communal.CommunalManager.CommunalModeListener);
+  }
+
+  @java.lang.FunctionalInterface public static interface CommunalManager.CommunalModeListener {
+    method public void onCommunalModeChanged(boolean);
+  }
+
+}
+
 package android.app.compat {
 
   public final class CompatChanges {
@@ -1924,8 +2033,8 @@
     method public void reportUsageStop(@NonNull android.app.Activity, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBucket(String, int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE) public void setAppStandbyBuckets(java.util.Map<java.lang.String,java.lang.Integer>);
-    method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTime(@NonNull String, long);
-    method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTimes(@NonNull java.util.Map<java.lang.String,java.lang.Long>);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTimeMillis(@NonNull String, long);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE) public void setEstimatedLaunchTimesMillis(@NonNull java.util.Map<java.lang.String,java.lang.Long>);
     method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void unregisterAppUsageLimitObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterAppUsageObserver(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_APP_USAGE) public void unregisterUsageSessionObserver(int);
@@ -2385,6 +2494,8 @@
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean canPairWithoutPrompt(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public java.util.List<android.companion.AssociationInfo> getAllAssociations();
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociatedForWifiConnection(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceAppeared(int);
+    method @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED) public void notifyDeviceDisappeared(int);
     method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void removeOnAssociationsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnAssociationsChangedListener);
   }
 
@@ -2444,7 +2555,6 @@
     method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri);
     method @Nullable @RequiresPermission("android.permission.CACHE_CONTENT") public android.os.Bundle getCache(@NonNull android.net.Uri);
     method @RequiresPermission("android.permission.CACHE_CONTENT") public void putCache(@NonNull android.net.Uri, @Nullable android.os.Bundle);
-    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public final void registerContentObserverForAllUsers(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver);
   }
 
   public abstract class Context {
@@ -2466,6 +2576,7 @@
     field public static final String BATTERY_STATS_SERVICE = "batterystats";
     field @Deprecated public static final int BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1048576; // 0x100000
     field public static final int BIND_ALLOW_FOREGROUND_SERVICE_STARTS_FROM_BACKGROUND = 262144; // 0x40000
+    field public static final String COMMUNAL_SERVICE = "communal";
     field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
     field public static final String CONTEXTHUB_SERVICE = "contexthub";
     field public static final String ETHERNET_SERVICE = "ethernet";
@@ -2854,13 +2965,16 @@
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract boolean arePermissionsIndividuallyControlled();
     method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.dex.ArtManager getArtManager();
-    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_SHARED_LIBRARIES) public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract String getDefaultBrowserPackageNameAsUser(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public CharSequence getHarmfulAppWarning(@NonNull String);
     method @Nullable public String getIncidentReportApproverPackageName();
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(@NonNull android.content.pm.PackageManager.PackageInfoFlags, int);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract android.graphics.drawable.Drawable getInstantAppIcon(String);
     method @Nullable public abstract android.content.ComponentName getInstantAppInstallerComponent();
     method @Nullable public abstract android.content.ComponentName getInstantAppResolverSettingsComponent();
@@ -2872,10 +2986,14 @@
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @Deprecated public abstract int installExistingPackage(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public abstract int installExistingPackage(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle);
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, int, android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
+    method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, int, @NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags, @NonNull android.os.UserHandle);
     method public abstract void registerDexModule(@NonNull String, @Nullable android.content.pm.PackageManager.DexModuleRegisterCallback);
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void replacePreferredActivity(@NonNull android.content.IntentFilter, int, @NonNull java.util.List<android.content.ComponentName>, @NonNull android.content.ComponentName);
@@ -2898,6 +3016,7 @@
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
     field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
     field public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
+    field public static final String FEATURE_GAME_SERVICE = "android.software.game_service";
     field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery";
     field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
@@ -3288,6 +3407,7 @@
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean);
   }
 
   public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener {
@@ -3845,6 +3965,127 @@
 
 }
 
+package android.hardware.input {
+
+  public final class VirtualKeyEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAction();
+    method public int getKeyCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ACTION_DOWN = 0; // 0x0
+    field public static final int ACTION_UP = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyEvent> CREATOR;
+  }
+
+  public static final class VirtualKeyEvent.Builder {
+    ctor public VirtualKeyEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualKeyEvent build();
+    method @NonNull public android.hardware.input.VirtualKeyEvent.Builder setAction(int);
+    method @NonNull public android.hardware.input.VirtualKeyEvent.Builder setKeyCode(int);
+  }
+
+  public class VirtualKeyboard implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
+  }
+
+  public class VirtualMouse implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent);
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent);
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent);
+  }
+
+  public final class VirtualMouseButtonEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAction();
+    method public int getButtonCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ACTION_BUTTON_PRESS = 11; // 0xb
+    field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc
+    field public static final int BUTTON_BACK = 8; // 0x8
+    field public static final int BUTTON_FORWARD = 16; // 0x10
+    field public static final int BUTTON_PRIMARY = 1; // 0x1
+    field public static final int BUTTON_SECONDARY = 2; // 0x2
+    field public static final int BUTTON_TERTIARY = 4; // 0x4
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseButtonEvent> CREATOR;
+  }
+
+  public static final class VirtualMouseButtonEvent.Builder {
+    ctor public VirtualMouseButtonEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualMouseButtonEvent build();
+    method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setAction(int);
+    method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setButtonCode(int);
+  }
+
+  public final class VirtualMouseRelativeEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public float getRelativeX();
+    method public float getRelativeY();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseRelativeEvent> CREATOR;
+  }
+
+  public static final class VirtualMouseRelativeEvent.Builder {
+    ctor public VirtualMouseRelativeEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualMouseRelativeEvent build();
+    method @NonNull public android.hardware.input.VirtualMouseRelativeEvent.Builder setRelativeX(float);
+    method @NonNull public android.hardware.input.VirtualMouseRelativeEvent.Builder setRelativeY(float);
+  }
+
+  public final class VirtualMouseScrollEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public float getXAxisMovement();
+    method public float getYAxisMovement();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseScrollEvent> CREATOR;
+  }
+
+  public static final class VirtualMouseScrollEvent.Builder {
+    ctor public VirtualMouseScrollEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualMouseScrollEvent build();
+    method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setXAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float);
+    method @NonNull public android.hardware.input.VirtualMouseScrollEvent.Builder setYAxisMovement(@FloatRange(from=-1.0F, to=1.0f) float);
+  }
+
+  public final class VirtualTouchEvent implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAction();
+    method public float getMajorAxisSize();
+    method public int getPointerId();
+    method public float getPressure();
+    method public int getToolType();
+    method public float getX();
+    method public float getY();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int ACTION_CANCEL = 3; // 0x3
+    field public static final int ACTION_DOWN = 0; // 0x0
+    field public static final int ACTION_MOVE = 2; // 0x2
+    field public static final int ACTION_UP = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchEvent> CREATOR;
+    field public static final int TOOL_TYPE_FINGER = 1; // 0x1
+    field public static final int TOOL_TYPE_PALM = 5; // 0x5
+  }
+
+  public static final class VirtualTouchEvent.Builder {
+    ctor public VirtualTouchEvent.Builder();
+    method @NonNull public android.hardware.input.VirtualTouchEvent build();
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setAction(int);
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setMajorAxisSize(@FloatRange(from=0.0f) float);
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setPointerId(int);
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setPressure(@FloatRange(from=0.0f) float);
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setToolType(int);
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setX(float);
+    method @NonNull public android.hardware.input.VirtualTouchEvent.Builder setY(float);
+  }
+
+  public class VirtualTouchscreen implements java.io.Closeable {
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
+  }
+
+}
+
 package android.hardware.lights {
 
   public final class LightState implements android.os.Parcelable {
@@ -3866,6 +4107,7 @@
   public class ContextHubClient implements java.io.Closeable {
     method public void close();
     method @NonNull public android.hardware.location.ContextHubInfo getAttachedHub();
+    method public int getId();
     method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessageToNanoApp(@NonNull android.hardware.location.NanoAppMessage);
   }
 
@@ -4186,13 +4428,24 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppMessage> CREATOR;
   }
 
+  public final class NanoAppRpcService implements android.os.Parcelable {
+    ctor public NanoAppRpcService(long, int);
+    method public int describeContents();
+    method public long getId();
+    method public int getVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppRpcService> CREATOR;
+  }
+
   public final class NanoAppState implements android.os.Parcelable {
     ctor public NanoAppState(long, int, boolean);
     ctor public NanoAppState(long, int, boolean, @NonNull java.util.List<java.lang.String>);
+    ctor public NanoAppState(long, int, boolean, @NonNull java.util.List<java.lang.String>, @NonNull java.util.List<android.hardware.location.NanoAppRpcService>);
     method public int describeContents();
     method public long getNanoAppId();
     method @NonNull public java.util.List<java.lang.String> getNanoAppPermissions();
     method public long getNanoAppVersion();
+    method @NonNull public java.util.List<android.hardware.location.NanoAppRpcService> getRpcServices();
     method public boolean isEnabled();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.location.NanoAppState> CREATOR;
@@ -5393,17 +5646,21 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void cancelMuteAwaitConnection(@NonNull android.media.AudioDeviceAttributes) throws java.lang.IllegalStateException;
     method public void clearAudioServerStateCallback();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+    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 @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getMutingExpectedDevice();
     method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -5411,7 +5668,10 @@
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
     method public boolean isHdmiSystemAudioSupported();
+    method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -5431,6 +5691,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
     method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
     field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
     field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
@@ -5450,6 +5711,15 @@
     method public void onAudioServerUp();
   }
 
+  public abstract static class AudioManager.MuteAwaitConnectionCallback {
+    ctor public AudioManager.MuteAwaitConnectionCallback();
+    method public void onMutedUntilConnection(@NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
+    method public void onUnmutedEvent(int, @NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
+    field public static final int EVENT_CANCEL = 3; // 0x3
+    field public static final int EVENT_CONNECTION = 1; // 0x1
+    field public static final int EVENT_TIMEOUT = 2; // 0x2
+  }
+
   @Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
     method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
   }
@@ -5682,9 +5952,11 @@
     method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
     method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
     method public int getFocusDuckingBehavior();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
     method public int getStatus();
     method public boolean removeUidDeviceAffinity(int);
     method public boolean removeUserIdDeviceAffinity(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
     method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void setRegistration(String);
     method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);
@@ -6254,6 +6526,7 @@
     method public int flush();
     method public long read(long);
     method public long read(@NonNull byte[], long, long);
+    method public long seek(long);
     method public void setFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
     method public int start();
     method public int stop();
@@ -6389,6 +6662,7 @@
 
   public class DownloadEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public int getDataLength();
+    method public int getDownloadId();
     method public int getItemFragmentIndex();
     method public int getItemId();
     method public int getLastItemFragmentIndex();
@@ -6398,11 +6672,13 @@
   public class DownloadSettings extends android.media.tv.tuner.filter.Settings {
     method @NonNull public static android.media.tv.tuner.filter.DownloadSettings.Builder builder(int);
     method public int getDownloadId();
+    method public boolean useDownloadId();
   }
 
   public static class DownloadSettings.Builder {
     method @NonNull public android.media.tv.tuner.filter.DownloadSettings build();
     method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setDownloadId(int);
+    method @NonNull public android.media.tv.tuner.filter.DownloadSettings.Builder setUseDownloadId(boolean);
   }
 
   public class Filter implements java.lang.AutoCloseable {
@@ -6501,12 +6777,14 @@
     method public long getAudioHandle();
     method public long getAvDataId();
     method public long getDataLength();
+    method public long getDts();
     method @Nullable public android.media.tv.tuner.filter.AudioDescriptor getExtraMetaData();
     method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
     method @IntRange(from=0) public int getMpuSequenceNumber();
     method public long getOffset();
     method public long getPts();
     method public int getStreamId();
+    method public boolean isDtsPresent();
     method public boolean isPrivateData();
     method public boolean isPtsPresent();
     method public boolean isSecureMemory();
@@ -6616,7 +6894,8 @@
   }
 
   public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
-    method public int getDataLength();
+    method @Deprecated public int getDataLength();
+    method public long getDataLengthLong();
     method public int getSectionNumber();
     method public int getTableId();
     method public int getVersion();
@@ -7348,6 +7627,7 @@
     method public int getSignalStrength();
     method public int getSnr();
     method public int getSpectralInversion();
+    method @NonNull public int[] getStreamIdList();
     method public int getSymbolRate();
     method @IntRange(from=0, to=65535) public int getSystemId();
     method public int getTransmissionMode();
@@ -7394,6 +7674,7 @@
     field public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH = 6; // 0x6
     field public static final int FRONTEND_STATUS_TYPE_SNR = 1; // 0x1
     field public static final int FRONTEND_STATUS_TYPE_SPECTRAL = 10; // 0xa
+    field public static final int FRONTEND_STATUS_TYPE_STREAM_ID_LIST = 39; // 0x27
     field public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE = 7; // 0x7
     field public static final int FRONTEND_STATUS_TYPE_T2_SYSTEM_ID = 29; // 0x1d
     field public static final int FRONTEND_STATUS_TYPE_TRANSMISSION_MODE = 27; // 0x1b
@@ -7576,7 +7857,7 @@
     method @NonNull public static android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder builder();
     method public int getCodeRate();
     method public int getModulation();
-    method @IntRange(from=0, to=255) public int getNumberOfSegment();
+    method @IntRange(from=0, to=255) public int getNumberOfSegments();
     method public int getTimeInterleaveMode();
   }
 
@@ -7584,7 +7865,7 @@
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings build();
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setCodeRate(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setModulation(int);
-    method @IntRange(from=0, to=255) @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setNumberOfSegment(int);
+    method @IntRange(from=0, to=255) @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setNumberOfSegments(int);
     method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setTimeInterleaveMode(int);
   }
 
@@ -8990,31 +9271,31 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearSeedAccountData();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @NonNull java.util.Set<java.lang.String>) throws android.os.UserManager.UserOperationException;
     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 @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getAllProfiles();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getEnabledProfiles();
+    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.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
-    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getRestrictedProfileParent();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getRestrictedProfileParent();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountType();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public long[] getSerialNumbersOfUsers(boolean);
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
-    method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
+    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 @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}) public boolean isAdminUser();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isCloneProfile();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isGuestUser();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
+    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.QUERY_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isManagedProfile(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isMediaSharedWithParent();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isPrimaryUser();
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isProfile();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isPrimaryUser();
+    method public boolean isProfile();
     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);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSameProfileGroup(@NonNull android.os.UserHandle, @NonNull android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
     method @RequiresPermission(android.Manifest.permission.MANAGE_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);
@@ -9022,6 +9303,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String);
+    field @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public static final String ACTION_CREATE_SUPERVISED_USER = "android.os.action.CREATE_SUPERVISED_USER";
     field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
     field @Deprecated public static final String DISALLOW_OEM_UNLOCK = "no_oem_unlock";
     field public static final String DISALLOW_RUN_IN_BACKGROUND = "no_run_in_background";
@@ -9507,6 +9789,7 @@
     field public static final String NAMESPACE_TELEPHONY = "telephony";
     field public static final String NAMESPACE_TETHERING = "tethering";
     field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
+    field public static final String NAMESPACE_UWB = "uwb";
     field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
   }
 
@@ -10451,6 +10734,18 @@
 
 }
 
+package android.service.games {
+
+  public class GameService extends android.app.Service {
+    ctor public GameService();
+    method @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
+    method public void onConnected();
+    method public void onDisconnected();
+    field public static final String SERVICE_INTERFACE = "android.service.games.GameService";
+  }
+
+}
+
 package android.service.notification {
 
   public final class Adjustment implements android.os.Parcelable {
@@ -12020,6 +12315,14 @@
     field public static final int ROAMING_TYPE_UNKNOWN = 1; // 0x1
   }
 
+  public final class SignalStrengthUpdateRequest implements android.os.Parcelable {
+    method public boolean isSystemThresholdReportingRequestedWhileIdle();
+  }
+
+  public static final class SignalStrengthUpdateRequest.Builder {
+    method @NonNull @RequiresPermission("android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH") public android.telephony.SignalStrengthUpdateRequest.Builder setSystemThresholdReportingRequestedWhileIdle(boolean);
+  }
+
   public final class SmsCbCmasInfo implements android.os.Parcelable {
     ctor public SmsCbCmasInfo(int, int, int, int, int, int);
     method public int describeContents();
@@ -12315,6 +12618,7 @@
   }
 
   public class TelephonyManager {
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void addCarrierPrivilegesListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) @WorkerThread public void bootstrapAuthenticationRequest(int, @NonNull android.net.Uri, @NonNull android.telephony.gba.UaSecurityProtocolIdentifier, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.BootstrapAuthenticationCallback);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void call(String, String);
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult changeIccLockPin(@NonNull String, @NonNull String);
@@ -12339,6 +12643,8 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCarrierPrivilegeStatus(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.List<java.lang.String> getCarrierPrivilegedPackagesForAllActiveSubscriptions();
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierRestrictionRules getCarrierRestrictionRules();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getCarrierServicePackageName();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getCarrierServicePackageNameForLogicalSlot(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaEnhancedRoamingIndicatorDisplayNumber();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMdn(int);
@@ -12381,9 +12687,13 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void iccCloseLogicalChannelByPort(int, int, int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(int, int, @Nullable String, int);
+    method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, @Nullable String);
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, @Nullable String);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
@@ -12410,6 +12720,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
     method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void removeCarrierPrivilegesListener(@NonNull android.telephony.TelephonyManager.CarrierPrivilegesListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestModemActivityInfo(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.ModemActivityInfo,android.telephony.TelephonyManager.ModemActivityInfoException>);
@@ -12586,6 +12897,10 @@
     field public static final int RESULT_SUCCESS = 0; // 0x0
   }
 
+  public static interface TelephonyManager.CarrierPrivilegesListener {
+    method public void onCarrierPrivilegesChanged(@NonNull java.util.List<java.lang.String>, @NonNull int[]);
+  }
+
   public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
     method public int getErrorCode();
     field public static final int ERROR_INVALID_INFO_RECEIVED = 2; // 0x2
@@ -12833,6 +13148,7 @@
     method public final int getSlotIndex();
     method public final void notifyApnUnthrottled(@NonNull String);
     method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>);
+    method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
     method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback);
     method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback);
     method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback);
@@ -12843,6 +13159,7 @@
   public class DataServiceCallback {
     method public void onApnUnthrottled(@NonNull String);
     method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>);
+    method public void onDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile);
     method public void onDeactivateDataCallComplete(int);
     method public void onRequestDataCallListComplete(int, @NonNull java.util.List<android.telephony.data.DataCallResponse>);
     method public void onSetDataProfileComplete(int);
@@ -12950,6 +13267,7 @@
     method public void removeNotificationFromList(String, int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>);
     method public void requestAllProfiles(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo[]>);
     method public void requestDefaultSmdpAddress(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.String>);
+    method public void requestEnabledProfileForPort(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>);
     method public void requestEuiccChallenge(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
     method public void requestEuiccInfo1(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
     method public void requestEuiccInfo2(String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>);
@@ -12973,6 +13291,7 @@
     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 = -4; // 0xfffffffc
     field public static final int RESULT_UNKNOWN_ERROR = -1; // 0xffffffff
   }
 
@@ -13231,8 +13550,10 @@
     field public static final int DEREGISTERED_REASON_UNKNOWN = 0; // 0x0
     field public static final int DEREGISTERING_REASON_DESTROY_PENDING = 6; // 0x6
     field public static final int DEREGISTERING_REASON_FEATURE_TAGS_CHANGING = 5; // 0x5
+    field public static final int DEREGISTERING_REASON_LOSING_PDN = 7; // 0x7
     field public static final int DEREGISTERING_REASON_PDN_CHANGE = 3; // 0x3
     field public static final int DEREGISTERING_REASON_PROVISIONING_CHANGE = 4; // 0x4
+    field public static final int DEREGISTERING_REASON_UNSPECIFIED = 8; // 0x8
   }
 
   public static final class DelegateRegistrationState.Builder {
@@ -13555,6 +13876,7 @@
     method public void disableIms(int);
     method public void enableIms(int);
     method public android.telephony.ims.stub.ImsConfigImplBase getConfig(int);
+    method @NonNull public java.util.concurrent.Executor getExecutor();
     method public long getImsServiceCapabilities();
     method public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int);
     method @Nullable public android.telephony.ims.stub.SipTransportImplBase getSipTransport(int);
@@ -13826,12 +14148,14 @@
   }
 
   public final class RcsClientConfiguration implements android.os.Parcelable {
-    ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+    ctor @Deprecated public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+    ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String, boolean);
     method public int describeContents();
     method @NonNull public String getClientVendor();
     method @NonNull public String getClientVersion();
     method @NonNull public String getRcsProfile();
     method @NonNull public String getRcsVersion();
+    method public boolean isRcsEnabledByUser();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR;
     field public static final String RCS_PROFILE_1_0 = "UP_1.0";
@@ -13968,6 +14292,7 @@
     field public static final int PUBLISH_STATE_NOT_PUBLISHED = 2; // 0x2
     field public static final int PUBLISH_STATE_OK = 1; // 0x1
     field public static final int PUBLISH_STATE_OTHER_ERROR = 6; // 0x6
+    field public static final int PUBLISH_STATE_PUBLISHING = 7; // 0x7
     field public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 4; // 0x4
     field public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 5; // 0x5
     field public static final int PUBLISH_STATE_VOICE_PROVISION_ERROR = 3; // 0x3
@@ -14170,6 +14495,7 @@
 
   public class MmTelFeature extends android.telephony.ims.feature.ImsFeature {
     ctor public MmTelFeature();
+    ctor public MmTelFeature(@NonNull java.util.concurrent.Executor);
     method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
     method public void changeOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
     method @Nullable public android.telephony.ims.ImsCallProfile createCallProfile(int, int);
@@ -14203,7 +14529,7 @@
   }
 
   public class RcsFeature extends android.telephony.ims.feature.ImsFeature {
-    ctor @Deprecated public RcsFeature();
+    ctor public RcsFeature();
     ctor public RcsFeature(@NonNull java.util.concurrent.Executor);
     method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
     method @NonNull public android.telephony.ims.stub.RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(@NonNull android.telephony.ims.stub.CapabilityExchangeEventListener);
@@ -14230,6 +14556,7 @@
 package android.telephony.ims.stub {
 
   public interface CapabilityExchangeEventListener {
+    method public default void onPublishUpdated(int, @NonNull String, int, @NonNull String) throws android.telephony.ims.ImsException;
     method public void onRemoteCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener.OptionsRequestCallback) throws android.telephony.ims.ImsException;
     method public void onRequestPublishCapabilities(int) throws android.telephony.ims.ImsException;
     method public void onUnpublish() throws android.telephony.ims.ImsException;
@@ -14307,6 +14634,7 @@
   }
 
   public class ImsConfigImplBase {
+    ctor public ImsConfigImplBase(@NonNull java.util.concurrent.Executor);
     ctor public ImsConfigImplBase();
     method public int getConfigInt(int);
     method public String getConfigString(int);
@@ -14359,6 +14687,7 @@
 
   public class ImsRegistrationImplBase {
     ctor public ImsRegistrationImplBase();
+    ctor public ImsRegistrationImplBase(@NonNull java.util.concurrent.Executor);
     method public final void onDeregistered(android.telephony.ims.ImsReasonInfo);
     method public final void onRegistered(int);
     method public final void onRegistered(@NonNull android.telephony.ims.ImsRegistrationAttributes);
@@ -14471,6 +14800,7 @@
   }
 
   public class SipTransportImplBase {
+    ctor public SipTransportImplBase();
     ctor public SipTransportImplBase(@NonNull java.util.concurrent.Executor);
     method public void createSipDelegate(int, @NonNull android.telephony.ims.DelegateRequest, @NonNull android.telephony.ims.DelegateStateCallback, @NonNull android.telephony.ims.DelegateMessageCallback);
     method public void destroySipDelegate(@NonNull android.telephony.ims.stub.SipDelegate, int);
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index 9cb9ddc..2c5acf1 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -60,14 +60,6 @@
 
 }
 
-package android.bluetooth {
-
-  public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
-    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
-  }
-
-}
-
 package android.content {
 
   public class Intent implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6b4d773..89dc678 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -42,6 +42,7 @@
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     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_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
@@ -352,6 +353,10 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
   }
 
+  public static final class StatusBarManager.DisableInfo {
+    method public boolean isRotationSuggestionDisabled();
+  }
+
   public final class SyncNotedAppOp implements android.os.Parcelable {
     ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
   }
@@ -437,10 +442,8 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
-    method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
     method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
-    method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
@@ -457,7 +460,6 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
     method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
     method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
     method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
@@ -465,22 +467,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
-    field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
-    field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
-    field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
-    field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
-    field public static final int CODE_HAS_PAIRED = 8; // 0x8
-    field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
-    field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
-    field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
-    field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
-    field public static final int CODE_OK = 0; // 0x0
-    field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
     field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
-    field public static final int CODE_SYSTEM_USER = 10; // 0xa
-    field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
-    field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
-    field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
     field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
     field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5
     field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18
@@ -522,66 +509,6 @@
     field public static final int OPERATION_SWITCH_USER = 2; // 0x2
     field public static final int OPERATION_UNINSTALL_CA_CERT = 40; // 0x28
     field public static final int OPERATION_WIPE_DATA = 8; // 0x8
-    field public static final int PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED = 3; // 0x3
-    field public static final int PROVISIONING_RESULT_PRE_CONDITION_FAILED = 1; // 0x1
-    field public static final int PROVISIONING_RESULT_PROFILE_CREATION_FAILED = 2; // 0x2
-    field public static final int PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED = 6; // 0x6
-    field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
-    field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
-    field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
-  }
-
-  public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
-    method public boolean canDeviceOwnerGrantSensorsPermissions();
-    method public int describeContents();
-    method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
-    method public long getLocalTime();
-    method @Nullable public java.util.Locale getLocale();
-    method @NonNull public String getOwnerName();
-    method @Nullable public String getTimeZone();
-    method public boolean isLeaveAllSystemAppsEnabled();
-    method public void logParams(@NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
-  }
-
-  public static final class FullyManagedDeviceProvisioningParams.Builder {
-    ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setDeviceOwnerCanGrantSensorsPermissions(boolean);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocale(@Nullable java.util.Locale);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String);
-  }
-
-  public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public android.accounts.Account getAccountToMigrate();
-    method @NonNull public String getOwnerName();
-    method @NonNull public android.content.ComponentName getProfileAdminComponentName();
-    method @Nullable public String getProfileName();
-    method public boolean isKeepAccountMigrated();
-    method public boolean isLeaveAllSystemAppsEnabled();
-    method public boolean isOrganizationOwnedProvisioning();
-    method public void logParams(@NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
-  }
-
-  public static final class ManagedProfileProvisioningParams.Builder {
-    ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepAccountMigrated(boolean);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String);
-  }
-
-  public class ProvisioningException extends android.util.AndroidException {
-    ctor public ProvisioningException(@NonNull Exception, int);
-    method public int getProvisioningResult();
   }
 
   public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
@@ -624,6 +551,14 @@
 
 }
 
+package android.app.communal {
+
+  public final class CommunalManager {
+    method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean);
+  }
+
+}
+
 package android.app.contentsuggestions {
 
   public final class ContentSuggestionsManager {
@@ -692,6 +627,14 @@
 
 }
 
+package android.companion {
+
+  public abstract class CompanionDeviceService extends android.app.Service {
+    method public void onBindCompanionDeviceService(@NonNull android.content.Intent);
+  }
+
+}
+
 package android.content {
 
   public final class AttributionSource implements android.os.Parcelable {
@@ -825,7 +768,8 @@
     method @Nullable public String getDefaultTextClassifierPackageName();
     method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken();
     method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
-    method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int);
     method @Nullable public abstract String[] getNamesForUids(int[]);
     method @NonNull public String getPermissionControllerPackageName();
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
@@ -835,6 +779,7 @@
     method public void holdLock(android.os.IBinder, int);
     method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
+    field public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
     field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
@@ -1067,13 +1012,13 @@
 
   public final class SensorPrivacyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, int, boolean);
   }
 
   public static class SensorPrivacyManager.Sources {
     field public static final int DIALOG = 3; // 0x3
     field public static final int OTHER = 5; // 0x5
     field public static final int QS_TILE = 1; // 0x1
+    field public static final int SAFETY_HUB = 6; // 0x6
     field public static final int SETTINGS = 2; // 0x2
     field public static final int SHELL = 4; // 0x4
   }
@@ -1445,6 +1390,8 @@
 
   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 public static final int[] getPublicStreamTypes();
@@ -1453,8 +1400,10 @@
     method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
     method public boolean hasRegisteredDynamicPolicy();
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
+    method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
     method public void setRampingRingerEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
   }
 
   public static final class AudioRecord.MetricsConstants {
@@ -1819,7 +1768,7 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public String getUserType();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
     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 isGuestUserEphemeral();
@@ -1965,6 +1914,7 @@
   }
 
   public class StorageManager {
+    method public long computeStorageCacheBytes(@NonNull java.io.File);
     method @NonNull public static java.util.UUID convert(@NonNull String);
     method @NonNull public static String convert(@NonNull java.util.UUID);
     method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
@@ -2203,8 +2153,8 @@
   }
 
   public class KeyStoreException extends java.lang.Exception {
-    ctor public KeyStoreException(int, String);
     method public int getErrorCode();
+    method public static boolean hasFailureInfoForError(int);
   }
 
 }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index ca9a468..343830a 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -399,6 +399,21 @@
     ],
 }
 
+aidl_interface {
+    name: "android.os.statsbootstrap_aidl",
+    unstable: true,
+    srcs: [
+        "android/os/IStatsBootstrapAtomService.aidl",
+        "android/os/StatsBootstrapAtom.aidl",
+        "android/os/StatsBootstrapAtomValue.aidl",
+    ],
+    backend: {
+        cpp: {
+            enabled: true,
+        },
+    },
+}
+
 // utility classes statically linked into wifi-service
 filegroup {
     name: "framework-wifi-service-shared-srcs",
diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
index 3c9b232..8e01779 100644
--- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
+++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -172,7 +172,7 @@
     private AccessibilityGestureEvent(@NonNull Parcel parcel) {
         mGestureId = parcel.readInt();
         mDisplayId = parcel.readInt();
-        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader());
+        ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader(), android.content.pm.ParceledListSlice.class);
         mMotionEvents = slice.getList();
     }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 09af72d..3d38551 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,10 +16,12 @@
 
 package android.accessibilityservice;
 
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
 import android.accessibilityservice.GestureDescription.MotionEventGenerator;
 import android.annotation.CallbackExecutor;
+import android.annotation.CheckResult;
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -520,7 +522,9 @@
     public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
 
     /**
-     * Action to toggle docking the current app's window
+     * Action to toggle docking the current app's window.
+     * <p>
+     * <strong>Note:</strong>  It is effective only if it appears in {@link #getSystemActions()}.
      */
     public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
 
@@ -1359,12 +1363,44 @@
         }
 
         /**
+         * Gets the {@link MagnificationConfig} of the controlling magnifier on the display.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will
+         * return null.
+         * </p>
+         *
+         * @return the magnification config that the service controls
+         */
+        public @Nullable MagnificationConfig getMagnificationConfig() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance(mService).getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.getMagnificationConfig(mDisplayId);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to obtain magnification config", re);
+                    re.rethrowFromSystemServer();
+                }
+            }
+            return null;
+        }
+
+        /**
          * Returns the current magnification scale.
          * <p>
          * <strong>Note:</strong> If the service is not yet connected (e.g.
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will
          * return a default value of {@code 1.0f}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API gets the scale of full-screen
+         * magnification. To get the scale of the current controlling magnifier,
+         * use {@link #getMagnificationConfig} instead.
+         * </p>
          *
          * @return the current magnification scale
          */
@@ -1393,6 +1429,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will
          * return a default value of {@code 0.0f}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API gets the center position of full-screen
+         * magnification. To get the magnification center of the current controlling magnifier,
+         * use {@link #getMagnificationConfig} instead.
+         * </p>
          *
          * @return the unscaled screen-relative X coordinate of the center of
          *         the magnified region
@@ -1422,6 +1464,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will
          * return a default value of {@code 0.0f}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API gets the center position of full-screen
+         * magnification. To get the magnification center of the current controlling magnifier,
+         * use {@link #getMagnificationConfig} instead.
+         * </p>
          *
          * @return the unscaled screen-relative Y coordinate of the center of
          *         the magnified region
@@ -1505,12 +1553,48 @@
         }
 
         /**
+         * Sets the {@link MagnificationConfig}. The service controls the magnification by
+         * setting the config.
+         * <p>
+         * <strong>Note:</strong> If the service is not yet connected (e.g.
+         * {@link AccessibilityService#onServiceConnected()} has not yet been
+         * called) or the service has been disconnected, this method will have
+         * no effect and return {@code false}.
+         * </p>
+         *
+         * @param config the magnification config
+         * @param animate {@code true} to animate from the current spec or
+         *                {@code false} to set the spec immediately
+         * @return {@code true} on success, {@code false} on failure
+         */
+        public boolean setMagnificationConfig(@NonNull MagnificationConfig config,
+                boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance(mService).getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationConfig(mDisplayId, config, animate);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to set magnification config", re);
+                    re.rethrowFromSystemServer();
+                }
+            }
+            return false;
+        }
+
+        /**
          * Sets the magnification scale.
          * <p>
          * <strong>Note:</strong> If the service is not yet connected (e.g.
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will have
          * no effect and return {@code false}.
+         * <p>
+         * <strong>Note:</strong> This legacy API sets the scale of full-screen
+         * magnification. To set the scale of the specified magnifier,
+         * use {@link #setMagnificationConfig} instead.
+         * </p>
          *
          * @param scale the magnification scale to set, must be >= 1 and <= 8
          * @param animate {@code true} to animate from the current scale or
@@ -1523,8 +1607,10 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.setMagnificationScaleAndCenter(mDisplayId,
-                            scale, Float.NaN, Float.NaN, animate);
+                    final MagnificationConfig config = new MagnificationConfig.Builder()
+                            .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                            .setScale(scale).build();
+                    return connection.setMagnificationConfig(mDisplayId, config, animate);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to set scale", re);
                     re.rethrowFromSystemServer();
@@ -1540,6 +1626,12 @@
          * {@link AccessibilityService#onServiceConnected()} has not yet been
          * called) or the service has been disconnected, this method will have
          * no effect and return {@code false}.
+         * </p>
+         * <p>
+         * <strong>Note:</strong> This legacy API sets the center of full-screen
+         * magnification. To set the center of the specified magnifier,
+         * use {@link #setMagnificationConfig} instead.
+         * </p>
          *
          * @param centerX the unscaled screen-relative X coordinate on which to
          *                center the viewport
@@ -1555,8 +1647,10 @@
                             mService.mConnectionId);
             if (connection != null) {
                 try {
-                    return connection.setMagnificationScaleAndCenter(mDisplayId,
-                            Float.NaN, centerX, centerY, animate);
+                    final MagnificationConfig config = new MagnificationConfig.Builder()
+                            .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                            .setCenterX(centerX).setCenterY(centerY).build();
+                    return connection.setMagnificationConfig(mDisplayId, config, animate);
                 } catch (RemoteException re) {
                     Log.w(LOG_TAG, "Failed to set center", re);
                     re.rethrowFromSystemServer();
@@ -1640,6 +1734,29 @@
         private ArrayMap<OnShowModeChangedListener, Handler> mListeners;
         private final Object mLock;
 
+        /** @hide */
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({
+                ENABLE_IME_SUCCESS,
+                ENABLE_IME_FAIL_BY_ADMIN,
+                ENABLE_IME_FAIL_UNKNOWN
+        })
+        public @interface EnableImeResult {}
+        /**
+         * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action succeeded.
+         */
+        public static final int ENABLE_IME_SUCCESS = 0;
+        /**
+         * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action failed
+         * because the InputMethod is not permitted by device policy manager.
+         */
+        public static final int ENABLE_IME_FAIL_BY_ADMIN = 1;
+        /**
+         * Return value for {@link #setInputMethodEnabled(String, boolean)}. The action failed
+         * and the reason is unknown.
+         */
+        public static final int ENABLE_IME_FAIL_UNKNOWN = 2;
+
         SoftKeyboardController(@NonNull AccessibilityService service, @NonNull Object lock) {
             mService = service;
             mLock = lock;
@@ -1868,6 +1985,39 @@
             }
             return false;
         }
+
+        /**
+         * Enable or disable the specified IME for the user for whom the service is activated. The
+         * IME needs to be in the same package as the service and needs to be allowed by device
+         * policy, if there is one. The change will persist until the specified IME is next
+         * explicitly enabled or disabled by whatever means, such as user choice, and may persist
+         * beyond the life cycle of the requesting service.
+         *
+         * @param imeId The ID of the input method to enable or disable. This IME must be installed.
+         * @param enabled {@code true} if the input method associated with {@code imeId} should be
+         *                enabled.
+         * @return status code for the result of enabling/disabling the input method associated
+         *         with {@code imeId}.
+         * @throws SecurityException if the input method is not in the same package as the service.
+         *
+         * @see android.view.inputmethod.InputMethodInfo#getId()
+         */
+        @CheckResult
+        @EnableImeResult
+        public int setInputMethodEnabled(@NonNull String imeId, boolean enabled)
+                throws SecurityException {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance(mService).getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setInputMethodEnabled(imeId, enabled);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+            return ENABLE_IME_FAIL_UNKNOWN;
+        }
     }
 
     /**
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 04c784e..1167d0b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -1094,8 +1094,8 @@
         mInteractiveUiTimeout = parcel.readInt();
         flags = parcel.readInt();
         crashed = parcel.readInt() != 0;
-        mComponentName = parcel.readParcelable(this.getClass().getClassLoader());
-        mResolveInfo = parcel.readParcelable(null);
+        mComponentName = parcel.readParcelable(this.getClass().getClassLoader(), android.content.ComponentName.class);
+        mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
         mSettingsActivityName = parcel.readString();
         mCapabilities = parcel.readInt();
         mSummaryResId = parcel.readInt();
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 81457eb..93e6914 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -17,6 +17,7 @@
 package android.accessibilityservice;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.MagnificationConfig;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Bitmap;
 import android.graphics.Region;
@@ -77,6 +78,8 @@
 
     oneway void setOnKeyEventResult(boolean handled, int sequence);
 
+    MagnificationConfig getMagnificationConfig(int displayId);
+
     float getMagnificationScale(int displayId);
 
     float getMagnificationCenterX(int displayId);
@@ -87,8 +90,7 @@
 
     boolean resetMagnification(int displayId, boolean animate);
 
-    boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX, float centerY,
-        boolean animate);
+    boolean setMagnificationConfig(int displayId, in MagnificationConfig config, boolean animate);
 
     void setMagnificationCallbackEnabled(int displayId, boolean enabled);
 
@@ -100,6 +102,8 @@
 
     boolean switchToInputMethod(String imeId);
 
+    int setInputMethodEnabled(String imeId, boolean enabled);
+
     boolean isAccessibilityButtonAvailable();
 
     void sendGesture(int sequence, in ParceledListSlice gestureSteps);
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 2c41e8d..3cbae99 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -23,6 +23,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Looper;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
@@ -74,6 +75,8 @@
 public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
     private static final String TAG = "ValueAnimator";
     private static final boolean DEBUG = false;
+    private static final boolean TRACE_ANIMATION_FRACTION = SystemProperties.getBoolean(
+            "persist.debug.animator.trace_fraction", false);
 
     /**
      * Internal constants
@@ -1554,6 +1557,10 @@
     @CallSuper
     @UnsupportedAppUsage
     void animateValue(float fraction) {
+        if (TRACE_ANIMATION_FRACTION) {
+            Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
+                    (int) (fraction * 1000));
+        }
         fraction = mInterpolator.getInterpolation(fraction);
         mCurrentFraction = fraction;
         int numValues = mValues.length;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9f8d246..a140983 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1903,7 +1903,7 @@
         public void readFromParcel(Parcel source) {
             id = source.readInt();
             persistentId = source.readInt();
-            childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader());
+            childrenTaskInfos = source.readArrayList(RecentTaskInfo.class.getClassLoader(), android.app.ActivityManager.RecentTaskInfo.class);
             lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
             lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
             lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 15f67d0..c0a8c1e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -38,10 +38,8 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
-import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UptimeMillisLong;
 import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
 import android.app.RemoteServiceException.CannotDeliverBroadcastException;
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
@@ -323,7 +321,8 @@
 
     @UnsupportedAppUsage
     private ContextImpl mSystemContext;
-    private final SparseArray<ContextImpl> mDisplaySystemUiContexts = new SparseArray<>();
+    @GuardedBy("this")
+    private SparseArray<ContextImpl> mDisplaySystemUiContexts;
 
     @UnsupportedAppUsage
     static volatile IPackageManager sPackageManager;
@@ -2547,9 +2546,18 @@
 
             if (packageInfo != null) {
                 if (!isLoadedApkResourceDirsUpToDate(packageInfo, aInfo)) {
-                    List<String> oldPaths = new ArrayList<>();
-                    LoadedApk.makePaths(this, aInfo, oldPaths);
-                    packageInfo.updateApplicationInfo(aInfo, oldPaths);
+                    if (packageInfo.getApplicationInfo().createTimestamp > aInfo.createTimestamp) {
+                        // The cached loaded apk is newer than the one passed in, we should not
+                        // update the cached version
+                        Slog.w(TAG, "getPackageInfo() called with an older ApplicationInfo "
+                                + "than the cached version for package " + aInfo.packageName);
+                    } else {
+                        Slog.v(TAG, "getPackageInfo() caused update to cached ApplicationInfo "
+                                + "for package " + aInfo.packageName);
+                        List<String> oldPaths = new ArrayList<>();
+                        LoadedApk.makePaths(this, aInfo, oldPaths);
+                        packageInfo.updateApplicationInfo(aInfo, oldPaths);
+                    }
                 }
 
                 return packageInfo;
@@ -2658,7 +2666,6 @@
         }
     }
 
-    @Override
     @NonNull
     public ContextImpl getSystemUiContext() {
         return getSystemUiContext(DEFAULT_DISPLAY);
@@ -2672,6 +2679,9 @@
     @NonNull
     public ContextImpl getSystemUiContext(int displayId) {
         synchronized (this) {
+            if (mDisplaySystemUiContexts == null) {
+                mDisplaySystemUiContexts = new SparseArray<>();
+            }
             ContextImpl systemUiContext = mDisplaySystemUiContexts.get(displayId);
             if (systemUiContext == null) {
                 systemUiContext = ContextImpl.createSystemUiContext(getSystemContext(), displayId);
@@ -2681,6 +2691,25 @@
         }
     }
 
+    @Nullable
+    @Override
+    public ContextImpl getSystemUiContextNoCreate() {
+        synchronized (this) {
+            if (mDisplaySystemUiContexts == null) return null;
+            return mDisplaySystemUiContexts.get(DEFAULT_DISPLAY);
+        }
+    }
+
+    void onSystemUiContextCleanup(ContextImpl context) {
+        synchronized (this) {
+            if (mDisplaySystemUiContexts == null) return;
+            final int index = mDisplaySystemUiContexts.indexOfValue(context);
+            if (index >= 0) {
+                mDisplaySystemUiContexts.removeAt(index);
+            }
+        }
+    }
+
     public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
         synchronized (this) {
             getSystemContext().installSystemApplicationInfo(info, classLoader);
@@ -4074,12 +4103,12 @@
     }
 
     private void handleStartBinderTracking() {
-        Binder.enableTracing();
+        Binder.enableStackTracking();
     }
 
     private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) {
         try {
-            Binder.disableTracing();
+            Binder.disableStackTracking();
             Binder.getTransactionTracker().writeTracesToFile(fd);
         } finally {
             IoUtils.closeQuietly(fd);
@@ -6618,7 +6647,7 @@
         boolean isAppProfileable = isAppDebuggable || data.appInfo.isProfileable();
         Trace.setAppTracingAllowed(isAppProfileable);
         if ((isAppProfileable || Build.IS_DEBUGGABLE) && data.enableBinderTracking) {
-            Binder.enableTracing();
+            Binder.enableStackTracking();
         }
 
         // Initialize heap profiling.
diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java
index bc698f6..b9ad5c3 100644
--- a/core/java/android/app/ActivityThreadInternal.java
+++ b/core/java/android/app/ActivityThreadInternal.java
@@ -28,7 +28,7 @@
 interface ActivityThreadInternal {
     ContextImpl getSystemContext();
 
-    ContextImpl getSystemUiContext();
+    ContextImpl getSystemUiContextNoCreate();
 
     boolean isInDensityCompatMode();
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 565f690..68c69e5 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -4058,7 +4058,7 @@
                 LongSparseArray<NoteOpEvent> array = new LongSparseArray<>(numEntries);
 
                 for (int i = 0; i < numEntries; i++) {
-                    array.put(source.readLong(), source.readParcelable(null));
+                    array.put(source.readLong(), source.readParcelable(null, android.app.AppOpsManager.NoteOpEvent.class));
                 }
 
                 return array;
@@ -5178,7 +5178,7 @@
             final int[] uids = parcel.createIntArray();
             if (!ArrayUtils.isEmpty(uids)) {
                 final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
-                        HistoricalOps.class.getClassLoader());
+                        HistoricalOps.class.getClassLoader(), android.content.pm.ParceledListSlice.class);
                 final List<HistoricalUidOps> uidOps = (listSlice != null)
                         ? listSlice.getList() : null;
                 if (uidOps == null) {
@@ -10000,7 +10000,7 @@
 
     private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel(
             @NonNull Parcel parcel) {
-        final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null);
+        final ParceledListSlice<AttributedOpEntry> listSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class);
         return listSlice == null ? null : listSlice.getList();
     }
 
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index d1b7145..44fb5db 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -210,16 +210,28 @@
     @Override
     public PackageInfo getPackageInfo(String packageName, int flags)
             throws NameNotFoundException {
+        return getPackageInfo(packageName, PackageInfoFlags.of(flags));
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(String packageName, PackageInfoFlags flags)
+            throws NameNotFoundException {
         return getPackageInfoAsUser(packageName, flags, getUserId());
     }
 
     @Override
     public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags)
             throws NameNotFoundException {
+        return getPackageInfo(versionedPackage, PackageInfoFlags.of(flags));
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(VersionedPackage versionedPackage, PackageInfoFlags flags)
+            throws NameNotFoundException {
         final int userId = getUserId();
         try {
             PackageInfo pi = mPM.getPackageInfoVersioned(versionedPackage,
-                    updateFlagsForPackage(flags, userId), userId);
+                    updateFlagsForPackage(flags.getValue(), userId), userId);
             if (pi != null) {
                 return pi;
             }
@@ -232,10 +244,16 @@
     @Override
     public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
+        return getPackageInfoAsUser(packageName, PackageInfoFlags.of(flags), userId);
+    }
+
+    @Override
+    public PackageInfo getPackageInfoAsUser(String packageName, PackageInfoFlags flags, int userId)
+            throws NameNotFoundException {
         PackageInfo pi =
                 getPackageInfoAsUserCached(
                         packageName,
-                        updateFlagsForPackage(flags, userId),
+                        updateFlagsForPackage(flags.getValue(), userId),
                         userId);
         if (pi == null) {
             throw new NameNotFoundException(packageName);
@@ -334,10 +352,16 @@
     @Override
     public int[] getPackageGids(String packageName, int flags)
             throws NameNotFoundException {
+        return getPackageGids(packageName, PackageInfoFlags.of(flags));
+    }
+
+    @Override
+    public int[] getPackageGids(String packageName, PackageInfoFlags flags)
+            throws NameNotFoundException {
         final int userId = getUserId();
         try {
             int[] gids = mPM.getPackageGids(packageName,
-                    updateFlagsForPackage(flags, userId), userId);
+                    updateFlagsForPackage(flags.getValue(), userId), userId);
             if (gids != null) {
                 return gids;
             }
@@ -350,6 +374,12 @@
 
     @Override
     public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
+        return getPackageUid(packageName, PackageInfoFlags.of(flags));
+    }
+
+    @Override
+    public int getPackageUid(String packageName, PackageInfoFlags flags)
+            throws NameNotFoundException {
         return getPackageUidAsUser(packageName, flags, getUserId());
     }
 
@@ -361,9 +391,15 @@
     @Override
     public int getPackageUidAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
+        return getPackageUidAsUser(packageName, PackageInfoFlags.of(flags), userId);
+    }
+
+    @Override
+    public int getPackageUidAsUser(String packageName, PackageInfoFlags flags, int userId)
+            throws NameNotFoundException {
         try {
             int uid = mPM.getPackageUid(packageName,
-                    updateFlagsForPackage(flags, userId), userId);
+                    updateFlagsForPackage(flags.getValue(), userId), userId);
             if (uid >= 0) {
                 return uid;
             }
@@ -448,15 +484,27 @@
     @Override
     public ApplicationInfo getApplicationInfo(String packageName, int flags)
             throws NameNotFoundException {
+        return getApplicationInfo(packageName, ApplicationInfoFlags.of(flags));
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, ApplicationInfoFlags flags)
+            throws NameNotFoundException {
         return getApplicationInfoAsUser(packageName, flags, getUserId());
     }
 
     @Override
     public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
+        return getApplicationInfoAsUser(packageName, ApplicationInfoFlags.of(flags), userId);
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfoAsUser(String packageName, ApplicationInfoFlags flags,
+            int userId) throws NameNotFoundException {
         ApplicationInfo ai = getApplicationInfoAsUserCached(
                         packageName,
-                        updateFlagsForApplication(flags, userId),
+                        updateFlagsForApplication(flags.getValue(), userId),
                         userId);
         if (ai == null) {
             throw new NameNotFoundException(packageName);
@@ -505,10 +553,16 @@
     @Override
     public ActivityInfo getActivityInfo(ComponentName className, int flags)
             throws NameNotFoundException {
+        return getActivityInfo(className, ComponentInfoFlags.of(flags));
+    }
+
+    @Override
+    public ActivityInfo getActivityInfo(ComponentName className, ComponentInfoFlags flags)
+            throws NameNotFoundException {
         final int userId = getUserId();
         try {
             ActivityInfo ai = mPM.getActivityInfo(className,
-                    updateFlagsForComponent(flags, userId, null), userId);
+                    updateFlagsForComponent(flags.getValue(), userId, null), userId);
             if (ai != null) {
                 return ai;
             }
@@ -522,10 +576,16 @@
     @Override
     public ActivityInfo getReceiverInfo(ComponentName className, int flags)
             throws NameNotFoundException {
+        return getReceiverInfo(className, ComponentInfoFlags.of(flags));
+    }
+
+    @Override
+    public ActivityInfo getReceiverInfo(ComponentName className, ComponentInfoFlags flags)
+            throws NameNotFoundException {
         final int userId = getUserId();
         try {
             ActivityInfo ai = mPM.getReceiverInfo(className,
-                    updateFlagsForComponent(flags, userId, null), userId);
+                    updateFlagsForComponent(flags.getValue(), userId, null), userId);
             if (ai != null) {
                 return ai;
             }
@@ -539,10 +599,16 @@
     @Override
     public ServiceInfo getServiceInfo(ComponentName className, int flags)
             throws NameNotFoundException {
+        return getServiceInfo(className, ComponentInfoFlags.of(flags));
+    }
+
+    @Override
+    public ServiceInfo getServiceInfo(ComponentName className, ComponentInfoFlags flags)
+            throws NameNotFoundException {
         final int userId = getUserId();
         try {
             ServiceInfo si = mPM.getServiceInfo(className,
-                    updateFlagsForComponent(flags, userId, null), userId);
+                    updateFlagsForComponent(flags.getValue(), userId, null), userId);
             if (si != null) {
                 return si;
             }
@@ -556,10 +622,16 @@
     @Override
     public ProviderInfo getProviderInfo(ComponentName className, int flags)
             throws NameNotFoundException {
+        return getProviderInfo(className, ComponentInfoFlags.of(flags));
+    }
+
+    @Override
+    public ProviderInfo getProviderInfo(ComponentName className, ComponentInfoFlags flags)
+            throws NameNotFoundException {
         final int userId = getUserId();
         try {
             ProviderInfo pi = mPM.getProviderInfo(className,
-                    updateFlagsForComponent(flags, userId, null), userId);
+                    updateFlagsForComponent(flags.getValue(), userId, null), userId);
             if (pi != null) {
                 return pi;
             }
@@ -582,16 +654,30 @@
     /** @hide */
     @Override
     public @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags) {
+        return this.getSharedLibraries(PackageInfoFlags.of(flags));
+    }
+
+    /** @hide
+     * @param flags */
+    @Override
+    public @NonNull List<SharedLibraryInfo> getSharedLibraries(PackageInfoFlags flags) {
         return getSharedLibrariesAsUser(flags, getUserId());
     }
 
     /** @hide */
     @Override
-    @SuppressWarnings("unchecked")
     public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+        return getSharedLibrariesAsUser(PackageInfoFlags.of(flags), userId);
+    }
+
+    /** @hide */
+    @Override
+    @SuppressWarnings("unchecked")
+    public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(PackageInfoFlags flags,
+            int userId) {
         try {
             ParceledListSlice<SharedLibraryInfo> sharedLibs = mPM.getSharedLibraries(
-                    mContext.getOpPackageName(), flags, userId);
+                    mContext.getOpPackageName(), flags.getValue(), userId);
             if (sharedLibs == null) {
                 return Collections.emptyList();
             }
@@ -604,10 +690,18 @@
     @NonNull
     @Override
     public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName,
-            @InstallFlags int flags) {
+            int flags) {
+        return getDeclaredSharedLibraries(packageName, PackageInfoFlags.of(flags));
+    }
+
+    @NonNull
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName,
+            PackageInfoFlags flags) {
         try {
             ParceledListSlice<SharedLibraryInfo> sharedLibraries = mPM.getDeclaredSharedLibraries(
-                    packageName, flags, mContext.getUserId());
+                    packageName, flags.getValue(), mContext.getUserId());
             return sharedLibraries != null ? sharedLibraries.getList() : Collections.emptyList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1092,6 +1186,12 @@
     @SuppressWarnings("unchecked")
     @Override
     public List<PackageInfo> getInstalledPackages(int flags) {
+        return getInstalledPackages(PackageInfoFlags.of(flags));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<PackageInfo> getInstalledPackages(PackageInfoFlags flags) {
         return getInstalledPackagesAsUser(flags, getUserId());
     }
 
@@ -1099,9 +1199,17 @@
     @Override
     @SuppressWarnings("unchecked")
     public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        return getInstalledPackagesAsUser(PackageInfoFlags.of(flags), userId);
+    }
+
+    /** @hide */
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<PackageInfo> getInstalledPackagesAsUser(PackageInfoFlags flags, int userId) {
         try {
             ParceledListSlice<PackageInfo> parceledList =
-                    mPM.getInstalledPackages(updateFlagsForPackage(flags, userId), userId);
+                    mPM.getInstalledPackages(updateFlagsForPackage(flags.getValue(), userId),
+                            userId);
             if (parceledList == null) {
                 return Collections.emptyList();
             }
@@ -1113,13 +1221,19 @@
 
     @SuppressWarnings("unchecked")
     @Override
-    public List<PackageInfo> getPackagesHoldingPermissions(
-            String[] permissions, int flags) {
+    public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
+        return this.getPackagesHoldingPermissions(permissions, PackageInfoFlags.of(flags));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<PackageInfo> getPackagesHoldingPermissions(String[] permissions,
+            PackageInfoFlags flags) {
         final int userId = getUserId();
         try {
             ParceledListSlice<PackageInfo> parceledList =
                     mPM.getPackagesHoldingPermissions(permissions,
-                            updateFlagsForPackage(flags, userId), userId);
+                            updateFlagsForPackage(flags.getValue(), userId), userId);
             if (parceledList == null) {
                 return Collections.emptyList();
             }
@@ -1135,13 +1249,27 @@
         return getInstalledApplicationsAsUser(flags, getUserId());
     }
 
+    @Override
+    public List<ApplicationInfo> getInstalledApplications(ApplicationInfoFlags flags) {
+        return getInstalledApplicationsAsUser(flags, getUserId());
+    }
+
     /** @hide */
     @SuppressWarnings("unchecked")
     @Override
     public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+        return getInstalledApplicationsAsUser(ApplicationInfoFlags.of(flags), userId);
+    }
+
+    /** @hide */
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<ApplicationInfo> getInstalledApplicationsAsUser(ApplicationInfoFlags flags,
+            int userId) {
         try {
             ParceledListSlice<ApplicationInfo> parceledList =
-                    mPM.getInstalledApplications(updateFlagsForApplication(flags, userId), userId);
+                    mPM.getInstalledApplications(updateFlagsForApplication(
+                            flags.getValue(), userId), userId);
             if (parceledList == null) {
                 return Collections.emptyList();
             }
@@ -1249,16 +1377,26 @@
 
     @Override
     public ResolveInfo resolveActivity(Intent intent, int flags) {
+        return resolveActivity(intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    public ResolveInfo resolveActivity(Intent intent, ResolveInfoFlags flags) {
         return resolveActivityAsUser(intent, flags, getUserId());
     }
 
     @Override
     public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
+        return resolveActivityAsUser(intent, ResolveInfoFlags.of(flags), userId);
+    }
+
+    @Override
+    public ResolveInfo resolveActivityAsUser(Intent intent, ResolveInfoFlags flags, int userId) {
         try {
             return mPM.resolveIntent(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                updateFlagsForComponent(flags, userId, intent),
+                updateFlagsForComponent(flags.getValue(), userId, intent),
                 userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1266,21 +1404,31 @@
     }
 
     @Override
-    public List<ResolveInfo> queryIntentActivities(Intent intent,
-                                                   int flags) {
+    public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+        return queryIntentActivities(intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentActivities(Intent intent, ResolveInfoFlags flags) {
         return queryIntentActivitiesAsUser(intent, flags, getUserId());
     }
 
     /** @hide Same as above but for a specific user */
     @Override
+    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
+        return queryIntentActivitiesAsUser(intent, ResolveInfoFlags.of(flags), userId);
+    }
+
+    /** @hide Same as above but for a specific user */
+    @Override
     @SuppressWarnings("unchecked")
-    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
-            int flags, int userId) {
+    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, ResolveInfoFlags flags,
+            int userId) {
         try {
             ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentActivities(
                     intent,
                     intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                    updateFlagsForComponent(flags, userId, intent),
+                    updateFlagsForComponent(flags.getValue(), userId, intent),
                     userId);
             if (parceledList == null) {
                 return Collections.emptyList();
@@ -1292,22 +1440,30 @@
     }
 
     @Override
-    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
             Intent intent, int flags) {
+        return queryIntentActivityOptions(caller,
+                specifics == null ? null : new ArrayList<>(Arrays.asList(specifics)),
+                intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
+            List<Intent> specifics, Intent intent, ResolveInfoFlags flags) {
         final int userId = getUserId();
         final ContentResolver resolver = mContext.getContentResolver();
 
         String[] specificTypes = null;
         if (specifics != null) {
-            final int N = specifics.length;
-            for (int i=0; i<N; i++) {
-                Intent sp = specifics[i];
+            final int numSpecifics = specifics.size();
+            for (int i = 0; i < numSpecifics; i++) {
+                Intent sp = specifics.get(i);
                 if (sp != null) {
                     String t = sp.resolveTypeIfNeeded(resolver);
                     if (t != null) {
                         if (specificTypes == null) {
-                            specificTypes = new String[N];
+                            specificTypes = new String[numSpecifics];
                         }
                         specificTypes[i] = t;
                     }
@@ -1318,11 +1474,11 @@
         try {
             ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentActivityOptions(
                     caller,
-                    specifics,
+                    specifics == null ? null : specifics.toArray(new Intent[0]),
                     specificTypes,
                     intent,
                     intent.resolveTypeIfNeeded(resolver),
-                    updateFlagsForComponent(flags, userId, intent),
+                    updateFlagsForComponent(flags.getValue(), userId, intent),
                     userId);
             if (parceledList == null) {
                 return Collections.emptyList();
@@ -1337,13 +1493,22 @@
      * @hide
      */
     @Override
-    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) {
+        return queryBroadcastReceiversAsUser(intent, ResolveInfoFlags.of(flags), userId);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, ResolveInfoFlags flags,
+            int userId) {
         try {
             ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentReceivers(
                     intent,
                     intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                    updateFlagsForComponent(flags, userId, intent),
+                    updateFlagsForComponent(flags.getValue(), userId, intent),
                     userId);
             if (parceledList == null) {
                 return Collections.emptyList();
@@ -1356,17 +1521,27 @@
 
     @Override
     public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+        return queryBroadcastReceivers(intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, ResolveInfoFlags flags) {
         return queryBroadcastReceiversAsUser(intent, flags, getUserId());
     }
 
     @Override
-    public ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags,
+    public ResolveInfo resolveServiceAsUser(Intent intent, int flags, @UserIdInt int userId) {
+        return resolveServiceAsUser(intent, ResolveInfoFlags.of(flags), userId);
+    }
+
+    @Override
+    public ResolveInfo resolveServiceAsUser(Intent intent, ResolveInfoFlags flags,
             @UserIdInt int userId) {
         try {
             return mPM.resolveService(
                 intent,
                 intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                updateFlagsForComponent(flags, userId, intent),
+                updateFlagsForComponent(flags.getValue(), userId, intent),
                 userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1375,17 +1550,28 @@
 
     @Override
     public ResolveInfo resolveService(Intent intent, int flags) {
+        return resolveService(intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    public ResolveInfo resolveService(Intent intent, ResolveInfoFlags flags) {
         return resolveServiceAsUser(intent, flags, getUserId());
     }
 
     @Override
-    @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
+        return queryIntentServicesAsUser(intent, ResolveInfoFlags.of(flags), userId);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, ResolveInfoFlags flags,
+            int userId) {
         try {
             ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentServices(
                     intent,
                     intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                    updateFlagsForComponent(flags, userId, intent),
+                    updateFlagsForComponent(flags.getValue(), userId, intent),
                     userId);
             if (parceledList == null) {
                 return Collections.emptyList();
@@ -1398,18 +1584,29 @@
 
     @Override
     public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+        return queryIntentServices(intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentServices(Intent intent, ResolveInfoFlags flags) {
         return queryIntentServicesAsUser(intent, flags, getUserId());
     }
 
     @Override
+    public List<ResolveInfo> queryIntentContentProvidersAsUser(
+            Intent intent, int flags, int userId) {
+        return queryIntentContentProvidersAsUser(intent, ResolveInfoFlags.of(flags), userId);
+    }
+
+    @Override
     @SuppressWarnings("unchecked")
     public List<ResolveInfo> queryIntentContentProvidersAsUser(
-            Intent intent, int flags, int userId) {
+            Intent intent, ResolveInfoFlags flags, int userId) {
         try {
             ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentContentProviders(
                     intent,
                     intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                    updateFlagsForComponent(flags, userId, intent),
+                    updateFlagsForComponent(flags.getValue(), userId, intent),
                     userId);
             if (parceledList == null) {
                 return Collections.emptyList();
@@ -1422,39 +1619,68 @@
 
     @Override
     public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {
+        return queryIntentContentProviders(intent, ResolveInfoFlags.of(flags));
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentContentProviders(Intent intent, ResolveInfoFlags flags) {
         return queryIntentContentProvidersAsUser(intent, flags, getUserId());
     }
 
     @Override
     public ProviderInfo resolveContentProvider(String name, int flags) {
+        return resolveContentProvider(name, ComponentInfoFlags.of(flags));
+    }
+
+    @Override
+    public ProviderInfo resolveContentProvider(String name, ComponentInfoFlags flags) {
         return resolveContentProviderAsUser(name, flags, getUserId());
     }
 
     /** @hide **/
     @Override
     public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
+        return resolveContentProviderAsUser(name, ComponentInfoFlags.of(flags), userId);
+    }
+
+    /** @hide **/
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(String name, ComponentInfoFlags flags,
+            int userId) {
         try {
             return mPM.resolveContentProvider(name,
-                    updateFlagsForComponent(flags, userId, null), userId);
+                    updateFlagsForComponent(flags.getValue(), userId, null), userId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     @Override
-    public List<ProviderInfo> queryContentProviders(String processName,
-            int uid, int flags) {
+    public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) {
+        return queryContentProviders(processName, uid, ComponentInfoFlags.of(flags));
+    }
+
+    @Override
+    public List<ProviderInfo> queryContentProviders(String processName, int uid,
+            ComponentInfoFlags flags) {
         return queryContentProviders(processName, uid, flags, null);
     }
 
     @Override
+    public List<ProviderInfo> queryContentProviders(String processName,
+            int uid, int flags, String metaDataKey) {
+        return queryContentProviders(processName, uid, ComponentInfoFlags.of(flags), metaDataKey);
+    }
+
+    @Override
     @SuppressWarnings("unchecked")
     public List<ProviderInfo> queryContentProviders(String processName,
-            int uid, int flags, String metaDataKey) {
+            int uid, ComponentInfoFlags flags, String metaDataKey) {
         try {
             ParceledListSlice<ProviderInfo> slice = mPM.queryContentProviders(processName, uid,
-                    updateFlagsForComponent(flags, UserHandle.getUserId(uid), null), metaDataKey);
-            return slice != null ? slice.getList() : Collections.<ProviderInfo>emptyList();
+                    updateFlagsForComponent(flags.getValue(), UserHandle.getUserId(uid),
+                            null), metaDataKey);
+            return slice != null ? slice.getList() : Collections.emptyList();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1839,7 +2065,7 @@
     /**
      * Update given flags when being used to request {@link PackageInfo}.
      */
-    private int updateFlagsForPackage(int flags, int userId) {
+    private long updateFlagsForPackage(long flags, int userId) {
         if ((flags & (GET_ACTIVITIES | GET_RECEIVERS | GET_SERVICES | GET_PROVIDERS)) != 0) {
             // Caller is asking for component details, so they'd better be
             // asking for specific Direct Boot matching behavior
@@ -1855,14 +2081,15 @@
     /**
      * Update given flags when being used to request {@link ApplicationInfo}.
      */
-    private int updateFlagsForApplication(int flags, int userId) {
+    private long updateFlagsForApplication(long flags, int userId) {
         return updateFlagsForPackage(flags, userId);
     }
 
     /**
      * Update given flags when being used to request {@link ComponentInfo}.
      */
-    private int updateFlagsForComponent(int flags, int userId, Intent intent) {
+    private long updateFlagsForComponent(@ComponentInfoFlagsBits long flags, int userId,
+            Intent intent) {
         if (intent != null) {
             if ((intent.getFlags() & Intent.FLAG_DIRECT_BOOT_AUTO) != 0) {
                 flags |= MATCH_DIRECT_BOOT_AUTO;
@@ -2109,18 +2336,24 @@
     }
 
     @Nullable
+    public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) {
+        return getPackageArchiveInfo(archiveFilePath, PackageInfoFlags.of(flags));
+    }
+
+    @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
-            @PackageInfoFlags int flags) {
-        if ((flags & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+            PackageInfoFlags flags) {
+        long flagsBits = flags.getValue();
+        if ((flagsBits & (PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                 | PackageManager.MATCH_DIRECT_BOOT_AWARE)) == 0) {
             // Caller expressed no opinion about what encryption
             // aware/unaware components they want to see, so match both
-            flags |= PackageManager.MATCH_DIRECT_BOOT_AWARE
+            flagsBits |= PackageManager.MATCH_DIRECT_BOOT_AWARE
                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
         }
 
-        boolean collectCertificates = (flags & PackageManager.GET_SIGNATURES) != 0
-                || (flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
+        boolean collectCertificates = (flagsBits & PackageManager.GET_SIGNATURES) != 0
+                || (flagsBits & PackageManager.GET_SIGNING_CERTIFICATES) != 0;
 
         ParseInput input = ParseTypeImpl.forParsingWithoutPlatformCompat().reset();
         ParseResult<ParsingPackage> result = ParsingPackageUtils.parseDefault(input,
@@ -2129,8 +2362,8 @@
         if (result.isError()) {
             return null;
         }
-        return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flags, 0, 0, null,
-                FrameworkPackageUserState.DEFAULT, UserHandle.getCallingUserId());
+        return PackageInfoWithoutStateUtils.generate(result.getResult(), null, flagsBits, 0, 0,
+                null, FrameworkPackageUserState.DEFAULT, UserHandle.getCallingUserId());
     }
 
     @Override
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 7a806bd..c0aebee 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -118,11 +118,11 @@
             name = source.readString();
         }
         interruptionFilter = source.readInt();
-        conditionId = source.readParcelable(null);
-        owner = source.readParcelable(null);
-        configurationActivity = source.readParcelable(null);
+        conditionId = source.readParcelable(null, android.net.Uri.class);
+        owner = source.readParcelable(null, android.content.ComponentName.class);
+        configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
         creationTime = source.readLong();
-        mZenPolicy = source.readParcelable(null);
+        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
         mModified = source.readInt() == ENABLED;
         mPkg = source.readString();
     }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 5b8cc70..3108d91 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -43,6 +43,8 @@
     private int mMaxManifestReceiverApiLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
     private boolean mDontSendToRestrictedApps = false;
     private boolean mAllowBackgroundActivityStarts;
+    private String[] mRequireAllOfPermissions;
+    private String[] mRequireNoneOfPermissions;
 
     /**
      * How long to temporarily put an app on the power allowlist when executing this broadcast
@@ -85,6 +87,20 @@
             "android:broadcast.allowBackgroundActivityStarts";
 
     /**
+     * Corresponds to {@link #setRequireAllOfPermissions}
+     * @hide
+     */
+    public static final String KEY_REQUIRE_ALL_OF_PERMISSIONS =
+            "android:broadcast.requireAllOfPermissions";
+
+    /**
+     * Corresponds to {@link #setRequireNoneOfPermissions}
+     * @hide
+     */
+    public static final String KEY_REQUIRE_NONE_OF_PERMISSIONS =
+            "android:broadcast.requireNoneOfPermissions";
+
+    /**
      * @hide
      * @deprecated Use {@link android.os.PowerExemptionManager#
      * TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -132,6 +148,8 @@
         mDontSendToRestrictedApps = opts.getBoolean(KEY_DONT_SEND_TO_RESTRICTED_APPS, false);
         mAllowBackgroundActivityStarts = opts.getBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS,
                 false);
+        mRequireAllOfPermissions = opts.getStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS);
+        mRequireNoneOfPermissions = opts.getStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS);
     }
 
     /**
@@ -323,6 +341,44 @@
     }
 
     /**
+     * Use this to configure a broadcast to be sent to apps that hold all permissions in
+     * the list. This is only for use with the {@link Context#sendBroadcast(Intent intent,
+     * @Nullable String receiverPermission, @Nullable Bundle options)}.
+     *
+     * <p> If both {@link #setRequireAllOfPermissions(String[])} and
+     * {@link #setRequireNoneOfPermissions(String[])} are used, then receivers must have all of the
+     * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the
+     * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast.
+     *
+     * @param requiredPermissions a list of Strings of permission the receiver must have, or null
+     *                            to clear any previously set value.
+     * @hide
+     */
+    @SystemApi
+    public void setRequireAllOfPermissions(@Nullable String[] requiredPermissions) {
+        mRequireAllOfPermissions = requiredPermissions;
+    }
+
+    /**
+     * Use this to configure a broadcast to be sent to apps that don't hold any permissions in
+     * list. This is only for use with the {@link Context#sendBroadcast(Intent intent,
+     * @Nullable String receiverPermission, @Nullable Bundle options)}.
+     *
+     * <p> If both {@link #setRequireAllOfPermissions(String[])} and
+     * {@link #setRequireNoneOfPermissions(String[])} are used, then receivers must have all of the
+     * permissions set by {@link #setRequireAllOfPermissions(String[])}, and none of the
+     * permissions set by {@link #setRequireNoneOfPermissions(String[])} to get the broadcast.
+     *
+     * @param excludedPermissions a list of Strings of permission the receiver must not have,
+     *                            or null to clear any previously set value.
+     * @hide
+     */
+    @SystemApi
+    public void setRequireNoneOfPermissions(@Nullable String[] excludedPermissions) {
+        mRequireNoneOfPermissions = excludedPermissions;
+    }
+
+    /**
      * 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.
@@ -351,6 +407,12 @@
         if (mAllowBackgroundActivityStarts) {
             b.putBoolean(KEY_ALLOW_BACKGROUND_ACTIVITY_STARTS, true);
         }
+        if (mRequireAllOfPermissions != null) {
+            b.putStringArray(KEY_REQUIRE_ALL_OF_PERMISSIONS, mRequireAllOfPermissions);
+        }
+        if (mRequireNoneOfPermissions != null) {
+            b.putStringArray(KEY_REQUIRE_NONE_OF_PERMISSIONS, mRequireNoneOfPermissions);
+        }
         return b.isEmpty() ? null : b;
     }
 }
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 8637e31..58f60a6 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -154,9 +154,12 @@
         int configDiff;
         boolean equivalent;
 
+        // Get theme outside of synchronization to avoid nested lock.
+        final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme();
+        final ContextImpl systemUiContext = mActivityThread.getSystemUiContextNoCreate();
+        final Resources.Theme systemUiTheme =
+                systemUiContext != null ? systemUiContext.getTheme() : null;
         synchronized (mResourcesManager) {
-            final Resources.Theme systemTheme = mActivityThread.getSystemContext().getTheme();
-            final Resources.Theme systemUiTheme = mActivityThread.getSystemUiContext().getTheme();
             if (mPendingConfiguration != null) {
                 if (!mPendingConfiguration.isOtherSeqNewer(config)) {
                     config = mPendingConfiguration;
@@ -207,7 +210,8 @@
                 systemTheme.rebase();
             }
 
-            if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
+            if (systemUiTheme != null
+                    && (systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
                 systemUiTheme.rebase();
             }
         }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4a7361e..f3e9f10 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1272,12 +1272,22 @@
         String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
         String[] receiverPermissions = receiverPermission == null ? null
                 : new String[] {receiverPermission};
+        String[] excludedPermissions = null;
+        if (options != null) {
+            String[] receiverPermissionsBundle = options.getStringArray(
+                    BroadcastOptions.KEY_REQUIRE_ALL_OF_PERMISSIONS);
+            if (receiverPermissionsBundle != null) {
+                receiverPermissions = receiverPermissionsBundle;
+            }
+            excludedPermissions = options.getStringArray(
+                    BroadcastOptions.KEY_REQUIRE_NONE_OF_PERMISSIONS);
+        }
         try {
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
                     mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
                     null, Activity.RESULT_OK, null, null, receiverPermissions,
-                    null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
+                    excludedPermissions, AppOpsManager.OP_NONE, options, false, false,
                     getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -3212,6 +3222,10 @@
     final void performFinalCleanup(String who, String what) {
         //Log.i(TAG, "Cleanup up context: " + this);
         mPackageInfo.removeContextRegistrations(getOuterContext(), who, what);
+        if (mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
+                && mToken instanceof WindowTokenClient) {
+            mMainThread.onSystemUiContextCleanup(this);
+        }
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 78759db..29e1b70 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -167,4 +167,16 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Called by games to communicate the current state to the platform.
+     * @param gameState An object set to the current state.
+     */
+    public void setGameState(@NonNull GameState gameState) {
+        try {
+            mService.setGameState(mContext.getPackageName(), gameState, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/app/GameState.aidl
similarity index 92%
rename from media/java/android/media/tv/TsRequest.aidl
rename to core/java/android/app/GameState.aidl
index 0abc7ec..b829e86 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/app/GameState.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.app;
 
-parcelable TsRequest;
+parcelable GameState;
\ No newline at end of file
diff --git a/core/java/android/app/GameState.java b/core/java/android/app/GameState.java
new file mode 100644
index 0000000..979dd34
--- /dev/null
+++ b/core/java/android/app/GameState.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * State of the game passed to the GameManager.
+ *
+ * This includes a top-level state for the game (indicating if the game can be interrupted without
+ * interfering with content that can't be paused). Since content can be loaded in any state, it
+ * includes an independent boolean flag to indicate loading status.
+ *
+ * Free-form metadata (as a Bundle) and a string description can also be specified by the
+ * application.
+ */
+public final class GameState implements Parcelable {
+    /**
+     * Default Game mode is unknown.
+     */
+    public static final int MODE_UNKNOWN = 0;
+
+    /**
+     * No mode means that the game is not in active play, for example the user is using the game
+     * menu.
+     */
+    public static final int MODE_NONE = 1;
+
+    /**
+     * Indicates if the game is in active, but interruptible, game play.
+     */
+    public static final int MODE_GAMEPLAY_INTERRUPTIBLE = 2;
+
+    /**
+     * Indicates if the game is in active user play mode, which is real time and cannot be
+     *  interrupted.
+     */
+    public static final int MODE_GAMEPLAY_UNINTERRUPTIBLE = 3;
+
+    /**
+     * Indicates that the current content shown is not gameplay related. For example it can be an
+     * ad, a web page, a text, or a video.
+     */
+    public static final int MODE_CONTENT = 4;
+
+    /**
+     * Implement the parcelable interface.
+     */
+    public static final @NonNull Creator<GameState> CREATOR = new Creator<GameState>() {
+        @Override
+        public GameState createFromParcel(Parcel in) {
+            return new GameState(in);
+        }
+
+        @Override
+        public GameState[] newArray(int size) {
+            return new GameState[size];
+        }
+    };
+
+    // Indicates if the game is loading assets/resources/compiling/etc. This is independent of game
+    // mode because there could be a loading UI displayed, or there could be loading in the
+    // background.
+    private final boolean mIsLoading;
+
+    // One of the states listed above.
+    private final @GameStateMode int mMode;
+
+    // This is a game specific description. For example can be level or scene name.
+    private final @Nullable String mDescription;
+
+    // This contains any other game specific parameters not covered by the fields above. It can be
+    // quality parameter data, settings, or game modes.
+    private final @NonNull Bundle mMetaData;
+
+    /**
+     * Create a GameState with the specified loading status.
+     * @param isLoading Whether the game is in the loading state.
+     * @param mode The game state mode of type @GameStateMode.
+     */
+    public GameState(boolean isLoading, @GameStateMode int mode) {
+        this(isLoading, mode, null, new Bundle());
+    }
+
+    /**
+     * Create a GameState with the given state variables.
+     * @param isLoading Whether the game is in the loading state.
+     * @param mode The game state mode of type @GameStateMode.
+     * @param description An optional description of the state.
+     * @param metaData Optional metadata.
+     */
+    public GameState(boolean isLoading, @GameStateMode int mode, @Nullable String description,
+            @NonNull Bundle metaData) {
+        mIsLoading = isLoading;
+        mMode = mode;
+        mDescription = description;
+        mMetaData = metaData;
+    }
+
+    private GameState(Parcel in) {
+        mIsLoading = in.readBoolean();
+        mMode = in.readInt();
+        mDescription = in.readString();
+        mMetaData = in.readBundle();
+    }
+
+    /**
+     * @return If the game is loading assets/resources/compiling/etc.
+     */
+    public boolean isLoading() {
+        return mIsLoading;
+    }
+
+    /**
+     * @return The game state mode.
+     */
+    public @GameStateMode int getMode() {
+        return mMode;
+    }
+
+    /**
+     * @return The state description, or null if one is not set.
+     */
+    public @Nullable String getDescription() {
+        return mDescription;
+    }
+
+    /**
+     * @return metadata associated with the state.
+     */
+    public @NonNull Bundle getMetadata() {
+        return mMetaData;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeBoolean(mIsLoading);
+        parcel.writeInt(mMode);
+        parcel.writeString(mDescription);
+        parcel.writeBundle(mMetaData);
+    }
+
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({MODE_UNKNOWN, MODE_NONE, MODE_GAMEPLAY_INTERRUPTIBLE, MODE_GAMEPLAY_UNINTERRUPTIBLE,
+            MODE_CONTENT
+
+    })
+    @interface GameStateMode {}
+}
diff --git a/core/java/android/app/GrantedUriPermission.java b/core/java/android/app/GrantedUriPermission.java
index 48d5b8c..a71cb4a 100644
--- a/core/java/android/app/GrantedUriPermission.java
+++ b/core/java/android/app/GrantedUriPermission.java
@@ -68,7 +68,7 @@
             };
 
     private GrantedUriPermission(Parcel in) {
-        uri = in.readParcelable(null);
+        uri = in.readParcelable(null, android.net.Uri.class);
         packageName = in.readString();
     }
 }
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8f904b5..a2578d6 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -520,6 +520,10 @@
     // descriptor.
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean stopBinderTrackingAndDump(in ParcelFileDescriptor fd);
+
+    /** Enables server-side binder tracing for the calling uid. */
+    void enableBinderTracing();
+
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void suppressResizeConfigChanges(boolean suppress);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 189f0a2..d9aa586 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import android.app.GameState;
+
 /**
  * @hide
  */
@@ -24,4 +26,5 @@
     void setGameMode(String packageName, int gameMode, int userId);
     int[] getAvailableGameModes(String packageName);
     boolean getAngleEnabled(String packageName, int userId);
+    void setGameState(String packageName, in GameState gameState, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 5750484..77af474 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1352,7 +1352,9 @@
 
         Application app = null;
 
-        String appClass = mApplicationInfo.className;
+        final String myProcessName = Process.myProcessName();
+        String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
+                myProcessName);
         if (forceDefaultAppClass || (appClass == null)) {
             appClass = "android.app.Application";
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index af907af..779552f1 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5808,6 +5808,7 @@
                     p, result);
             buildCustomContentIntoTemplate(mContext, standard, customContent,
                     p, result);
+            makeHeaderExpanded(standard);
             return standard;
         }
 
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index cd6df0b..f97415c 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -100,7 +100,7 @@
         } else {
             mDescription = null;
         }
-        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
+        in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mBlocked = in.readBoolean();
         mUserLockedFields = in.readInt();
     }
diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java
index eb9ec86..b40fbee 100644
--- a/core/java/android/app/NotificationHistory.java
+++ b/core/java/android/app/NotificationHistory.java
@@ -497,7 +497,11 @@
         p.writeLong(notification.getPostedTimeMs());
         p.writeString(notification.getTitle());
         p.writeString(notification.getText());
-        notification.getIcon().writeToParcel(p, flags);
+        p.writeBoolean(false);
+        // The current design does not display icons, so don't bother adding them to the parcel
+        //if (notification.getIcon() != null) {
+        //    notification.getIcon().writeToParcel(p, flags);
+        //}
     }
 
     /**
@@ -539,7 +543,9 @@
         notificationOut.setPostedTimeMs(p.readLong());
         notificationOut.setTitle(p.readString());
         notificationOut.setText(p.readString());
-        notificationOut.setIcon(Icon.CREATOR.createFromParcel(p));
+        if (p.readBoolean()) {
+            notificationOut.setIcon(Icon.CREATOR.createFromParcel(p));
+        }
 
         return notificationOut.build();
     }
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index e099716..068304d 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -31,6 +31,7 @@
 per-file *UserSwitchObserver* = file:/services/core/java/com/android/server/am/OWNERS
 per-file UiAutomation.java = file:/services/accessibility/OWNERS
 per-file GameManager* = file:/GAME_MANAGER_OWNERS
+per-file GameState* = file:/GAME_MANAGER_OWNERS
 per-file IGameManager* = file:/GAME_MANAGER_OWNERS
 
 # ActivityThread
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 2c0f983..bc78df5 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -39,7 +39,7 @@
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.PackageManager.ResolveInfoFlagsBits;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
@@ -1296,7 +1296,7 @@
     @RequiresPermission(permission.GET_INTENT_SENDER_INTENT)
     @SystemApi(client = Client.MODULE_LIBRARIES)
     @TestApi
-    public @NonNull List<ResolveInfo> queryIntentComponents(@ResolveInfoFlags int flags) {
+    public @NonNull List<ResolveInfo> queryIntentComponents(@ResolveInfoFlagsBits int flags) {
         try {
             ParceledListSlice<ResolveInfo> parceledList = ActivityManager.getService()
                     .queryIntentComponentsForIntentSender(mTarget, flags);
diff --git a/core/java/android/app/RemoteInputHistoryItem.java b/core/java/android/app/RemoteInputHistoryItem.java
index 091db3f..32f8981 100644
--- a/core/java/android/app/RemoteInputHistoryItem.java
+++ b/core/java/android/app/RemoteInputHistoryItem.java
@@ -48,7 +48,7 @@
     protected RemoteInputHistoryItem(Parcel in) {
         mText = in.readCharSequence();
         mMimeType = in.readStringNoHelper();
-        mUri = in.readParcelable(Uri.class.getClassLoader());
+        mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
     }
 
     public static final Creator<RemoteInputHistoryItem> CREATOR =
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index be6a31e..7635138 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -315,6 +315,16 @@
     private static final String TAG = "Service";
 
     /**
+     * Selector for {@link #stopForeground(int)}:  equivalent to passing {@code false}
+     * to the legacy API {@link #stopForeground(boolean)}.
+     *
+     * @deprecated Use {@link #STOP_FOREGROUND_DETACH} instead.  The legacy
+     * behavior was inconsistent, leading to bugs around unpredictable results.
+     */
+    @Deprecated
+    public static final int STOP_FOREGROUND_LEGACY = 0;
+
+    /**
      * Selector for {@link #stopForeground(int)}: if supplied, the notification previously
      * supplied to {@link #startForeground} will be cancelled and removed from display.
      */
@@ -329,6 +339,7 @@
 
     /** @hide */
     @IntDef(flag = false, prefix = { "STOP_FOREGROUND_" }, value = {
+            STOP_FOREGROUND_LEGACY,
             STOP_FOREGROUND_REMOVE,
             STOP_FOREGROUND_DETACH
     })
@@ -795,15 +806,17 @@
      * Legacy version of {@link #stopForeground(int)}.
      * @param removeNotification If true, the {@link #STOP_FOREGROUND_REMOVE}
      * selector will be passed to {@link #stopForeground(int)}; otherwise
-     * {@code zero} will be passed.
+     * {@link #STOP_FOREGROUND_LEGACY} will be passed.
      * @see #stopForeground(int)
      * @see #startForeground(int, Notification)
      *
-     * @deprecated use {@link #stopForeground(int)} instead.
+     * @deprecated call {@link #stopForeground(int)} and pass either
+     * {@link #STOP_FOREGROUND_REMOVE} or {@link #STOP_FOREGROUND_DETACH}
+     * explicitly instead.
      */
     @Deprecated
     public final void stopForeground(boolean removeNotification) {
-        stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : 0);
+        stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROUND_LEGACY);
     }
 
     /**
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 48ceef0..ae578f5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -165,7 +165,7 @@
      *
      * @hide
      */
-    public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_ROTATE_SUGGESTIONS;
+    public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_NONE;
 
     /**
      * disable flags to be applied when the device is sim-locked.
@@ -626,6 +626,9 @@
      * foreground ({@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND}
      * and the {@link android.service.quicksettings.TileService} must be exported.
      *
+     * Note: the system can choose to auto-deny a request if the user has denied that specific
+     * request (user, ComponentName) enough times before.
+     *
      * @param tileServiceComponentName {@link ComponentName} of the
      *        {@link android.service.quicksettings.TileService} for the request.
      * @param tileLabel label of the tile to show to the user.
@@ -709,6 +712,7 @@
         private boolean mSystemIcons;
         private boolean mClock;
         private boolean mNotificationIcons;
+        private boolean mRotationSuggestion;
 
         /** @hide */
         public DisableInfo(int flags1, int flags2) {
@@ -720,6 +724,7 @@
             mSystemIcons = (flags1 & DISABLE_SYSTEM_INFO) != 0;
             mClock = (flags1 & DISABLE_CLOCK) != 0;
             mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0;
+            mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0;
         }
 
         /** @hide */
@@ -843,14 +848,24 @@
         }
 
         /**
-         * @return {@code true} if no components are disabled (default state)
+         * Returns whether the rotation suggestion is disabled.
          *
          * @hide
          */
+        @TestApi
+        public boolean isRotationSuggestionDisabled() {
+            return mRotationSuggestion;
+        }
+
+        /**
+         * @return {@code true} if no components are disabled (default state)
+         * @hide
+         */
         @SystemApi
         public boolean areAllComponentsEnabled() {
             return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents
-                    && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons;
+                    && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons
+                    && !mRotationSuggestion;
         }
 
         /** @hide */
@@ -863,6 +878,7 @@
             mSystemIcons = false;
             mClock = false;
             mNotificationIcons = false;
+            mRotationSuggestion = false;
         }
 
         /**
@@ -872,7 +888,8 @@
          */
         public boolean areAllComponentsDisabled() {
             return mStatusBarExpansion && mNavigateHome && mNotificationPeeking
-                    && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons;
+                    && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons
+                    && mRotationSuggestion;
         }
 
         /** @hide */
@@ -885,6 +902,7 @@
             mSystemIcons = true;
             mClock = true;
             mNotificationIcons = true;
+            mRotationSuggestion = true;
         }
 
         @NonNull
@@ -901,6 +919,7 @@
             sb.append(" mSystemIcons=").append(mSystemIcons ? "disabled" : "enabled");
             sb.append(" mClock=").append(mClock ? "disabled" : "enabled");
             sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled");
+            sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled");
 
             return sb.toString();
 
@@ -924,6 +943,7 @@
             if (mSystemIcons) disable1 |= DISABLE_SYSTEM_INFO;
             if (mClock) disable1 |= DISABLE_CLOCK;
             if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS;
+            if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS;
 
             return new Pair<Integer, Integer>(disable1, disable2);
         }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 089c269..85ddff9 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -131,10 +131,12 @@
 import android.media.tv.tunerresourcemanager.ITunerResourceManager;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.net.ConnectivityFrameworkInitializer;
+import android.net.ConnectivityFrameworkInitializerTiramisu;
 import android.net.EthernetManager;
 import android.net.IEthernetManager;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
 import android.net.IPacProxyManager;
 import android.net.IVpnManager;
 import android.net.IpSecManager;
@@ -146,8 +148,6 @@
 import android.net.VpnManager;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.LowpanManager;
-import android.net.nsd.INsdManager;
-import android.net.nsd.NsdManager;
 import android.net.vcn.IVcnManagementService;
 import android.net.vcn.VcnManager;
 import android.net.wifi.WifiFrameworkInitializer;
@@ -228,6 +228,8 @@
 import android.view.contentcapture.IContentCaptureManager;
 import android.view.displayhash.DisplayHashManager;
 import android.view.inputmethod.InputMethodManager;
+import android.view.selectiontoolbar.ISelectionToolbarManager;
+import android.view.selectiontoolbar.SelectionToolbarManager;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textservice.TextServicesManager;
 import android.view.translation.ITranslationManager;
@@ -367,6 +369,15 @@
                 return new TextClassificationManager(ctx);
             }});
 
+        registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
+                new CachedServiceFetcher<SelectionToolbarManager>() {
+                    @Override
+                    public SelectionToolbarManager createService(ContextImpl ctx) {
+                        IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE);
+                        return new SelectionToolbarManager(ctx.getOuterContext(),
+                                ISelectionToolbarManager.Stub.asInterface(b));
+                    }});
+
         registerService(Context.FONT_SERVICE, FontManager.class,
                 new CachedServiceFetcher<FontManager>() {
             @Override
@@ -576,15 +587,6 @@
                     ctx.mMainThread.getHandler());
             }});
 
-        registerService(Context.NSD_SERVICE, NsdManager.class,
-                new CachedServiceFetcher<NsdManager>() {
-            @Override
-            public NsdManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                IBinder b = ServiceManager.getServiceOrThrow(Context.NSD_SERVICE);
-                INsdManager service = INsdManager.Stub.asInterface(b);
-                return new NsdManager(ctx.getOuterContext(), service);
-            }});
-
         registerService(Context.PEOPLE_SERVICE, PeopleManager.class,
                 new CachedServiceFetcher<PeopleManager>() {
             @Override
@@ -1012,7 +1014,11 @@
                 new CachedServiceFetcher<NetworkStatsManager>() {
             @Override
             public NetworkStatsManager createService(ContextImpl ctx) throws ServiceNotFoundException {
-                return new NetworkStatsManager(ctx.getOuterContext());
+                // TODO: Replace with an initializer in the module, see
+                //  {@code ConnectivityFrameworkInitializer}.
+                final INetworkStatsService service = INetworkStatsService.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE));
+                return new NetworkStatsManager(ctx.getOuterContext(), service);
             }});
 
         registerService(Context.PERSISTENT_DATA_BLOCK_SERVICE, PersistentDataBlockManager.class,
@@ -1513,7 +1519,7 @@
                     }
                 });
 
-        registerService(Context.COMMUNAL_MANAGER_SERVICE, CommunalManager.class,
+        registerService(Context.COMMUNAL_SERVICE, CommunalManager.class,
                 new CachedServiceFetcher<CommunalManager>() {
                     @Override
                     public CommunalManager createService(ContextImpl ctx) {
@@ -1522,7 +1528,7 @@
                             return null;
                         }
                         IBinder iBinder =
-                                ServiceManager.getService(Context.COMMUNAL_MANAGER_SERVICE);
+                                ServiceManager.getService(Context.COMMUNAL_SERVICE);
                         return iBinder != null ? new CommunalManager(
                                 ICommunalManager.Stub.asInterface(iBinder)) : null;
                     }
@@ -1547,6 +1553,7 @@
             SupplementalProcessFrameworkInitializer.registerServiceWrappers();
             UwbFrameworkInitializer.registerServiceWrappers();
             SafetyCenterFrameworkInitializer.registerServiceWrappers();
+            ConnectivityFrameworkInitializerTiramisu.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...
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 61bf9b3..772492d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1489,18 +1489,27 @@
                     mContext.getUserId());
             if (fd != null) {
                 FileOutputStream fos = null;
-                boolean ok = false;
+                final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid));
                 try {
-                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
-                    copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
-                    // The 'close()' is the trigger for any server-side image manipulation,
-                    // so we must do that before waiting for completion.
-                    fos.close();
-                    completion.waitForCompletion();
+                    // If the stream can't be decoded, treat it as an invalid input.
+                    if (tmp != null) {
+                        fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                        tmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
+                        // The 'close()' is the trigger for any server-side image manipulation,
+                        // so we must do that before waiting for completion.
+                        fos.close();
+                        completion.waitForCompletion();
+                    } else {
+                        throw new IllegalArgumentException(
+                                "Resource 0x" + Integer.toHexString(resid) + " is invalid");
+                    }
                 } finally {
                     // Might be redundant but completion shouldn't wait unless the write
                     // succeeded; this is a fallback if it threw past the close+wait.
                     IoUtils.closeQuietly(fos);
+                    if (tmp != null) {
+                        tmp.recycle();
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -1742,13 +1751,22 @@
                     result, which, completion, mContext.getUserId());
             if (fd != null) {
                 FileOutputStream fos = null;
+                final Bitmap tmp = BitmapFactory.decodeStream(bitmapData);
                 try {
-                    fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
-                    copyStreamToWallpaperFile(bitmapData, fos);
-                    fos.close();
-                    completion.waitForCompletion();
+                    // If the stream can't be decoded, treat it as an invalid input.
+                    if (tmp != null) {
+                        fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+                        tmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
+                        fos.close();
+                        completion.waitForCompletion();
+                    } else {
+                        throw new IllegalArgumentException("InputStream is invalid");
+                    }
                 } finally {
                     IoUtils.closeQuietly(fos);
+                    if (tmp != null) {
+                        tmp.recycle();
+                    }
                 }
             }
         } catch (RemoteException e) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cf95ffe..7969cda 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -470,6 +470,105 @@
             = "android.app.action.PROVISION_FINALIZATION";
 
     /**
+     * Activity action: starts the managed profile provisioning flow inside the device management
+     * role holder.
+     *
+     * <p>During the managed profile provisioning flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>A result code of {@link Activity#RESULT_OK} implies that managed profile provisioning
+     * finished successfully. If it did not, a result code of {@link Activity#RESULT_CANCELED}
+     * is used instead.
+     *
+     * @see #ACTION_PROVISION_MANAGED_PROFILE
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_PROFILE =
+            "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_PROFILE";
+
+    /**
+     * Result code that can be returned by the {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if a work
+     * profile has been created.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_WORK_PROFILE_CREATED = 122;
+
+    /**
+     * Result code that can be returned by the {@link
+     * #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} or {@link
+     * #ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE} intent handlers if the
+     * device owner was set.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_DEVICE_OWNER_SET = 123;
+
+    /**
+     * Activity action: starts the trusted source provisioning flow inside the device management
+     * role holder.
+     *
+     * <p>During the trusted source provisioning flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>The result codes can be either {@link #RESULT_WORK_PROFILE_CREATED}, {@link
+     * #RESULT_DEVICE_OWNER_SET} or {@link Activity#RESULT_CANCELED} if provisioning failed.
+     *
+     * @see #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE =
+            "android.app.action.ROLE_HOLDER_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+
+    /**
+     * Activity action: starts the provisioning finalization flow inside the device management
+     * role holder.
+     *
+     * <p>During the provisioning finalization flow, the platform-provided provisioning handler
+     * will delegate provisioning to the device management role holder, by firing this intent.
+     * Third-party mobile device management applications attempting to fire this intent will
+     * receive a {@link SecurityException}.
+     *
+     * <p>Device management role holders are required to have a handler for this intent action.
+     *
+     * <p>This handler forwards the result from the admin app's {@link
+     * #ACTION_ADMIN_POLICY_COMPLIANCE} handler. Result code {@link Activity#RESULT_CANCELED}
+     * implies the provisioning finalization flow has failed.
+     *
+     * @see #ACTION_PROVISION_FINALIZATION
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_ROLE_HOLDER_PROVISION_FINALIZATION =
+            "android.app.action.ROLE_HOLDER_PROVISION_FINALIZATION";
+
+    /**
      * Action: Bugreport sharing with device owner has been accepted by the user.
      *
      * @hide
@@ -2059,13 +2158,24 @@
     /**
      * Result code for {@link #checkProvisioningPreCondition}.
      *
+     * <p>Unknown error code returned  for {@link #ACTION_PROVISION_MANAGED_DEVICE},
+     * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int CODE_UNKNOWN_ERROR = -1;
+
+    /**
+     * Result code for {@link #checkProvisioningPreCondition}.
+     *
      * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
      * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
      * when provisioning is allowed.
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_OK = 0;
 
     /**
@@ -2076,7 +2186,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_HAS_DEVICE_OWNER = 1;
 
     /**
@@ -2087,7 +2197,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_USER_HAS_PROFILE_OWNER = 2;
 
     /**
@@ -2097,7 +2207,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_USER_NOT_RUNNING = 3;
 
     /**
@@ -2108,7 +2218,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_USER_SETUP_COMPLETED = 4;
 
     /**
@@ -2116,7 +2226,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_NONSYSTEM_USER_EXISTS = 5;
 
     /**
@@ -2124,7 +2234,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_ACCOUNTS_NOT_EMPTY = 6;
 
     /**
@@ -2134,7 +2244,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_NOT_SYSTEM_USER = 7;
 
     /**
@@ -2145,7 +2255,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_HAS_PAIRED = 8;
 
     /**
@@ -2157,7 +2267,7 @@
      * @see {@link PackageManager#FEATURE_MANAGED_USERS}
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9;
 
     /**
@@ -2169,7 +2279,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_SYSTEM_USER = 10;
 
     /**
@@ -2180,7 +2290,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
 
     /**
@@ -2190,7 +2300,6 @@
      * @deprecated not used anymore but can't be removed since it's a @TestApi.
      **/
     @Deprecated
-    @TestApi
     public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
 
     /**
@@ -2202,7 +2311,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
 
     /**
@@ -2224,7 +2333,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15;
 
     /**
@@ -2235,8 +2344,8 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "CODE_" }, value = {
-            CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, CODE_USER_NOT_RUNNING,
-            CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
+            CODE_UNKNOWN_ERROR, CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER,
+            CODE_USER_NOT_RUNNING, CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
             CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
             CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
             CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
@@ -2245,88 +2354,6 @@
     public @interface ProvisioningPreCondition {}
 
     /**
-     * Service-specific error code for {@link #provisionFullyManagedDevice} and
-     * {@link #createAndProvisionManagedProfile}:
-     * Indicates the call to {@link #checkProvisioningPreCondition} returned an error code.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_PRE_CONDITION_FAILED = 1;
-
-    /**
-     * Service-specific error code for {@link #createAndProvisionManagedProfile}:
-     * Indicates the call to {@link UserManager#createProfileForUserEvenWhenDisallowed}
-     * returned {@code null}.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_PROFILE_CREATION_FAILED = 2;
-
-    /**
-     * Service-specific error code for {@link #createAndProvisionManagedProfile}:
-     * Indicates the call to {@link PackageManager#installExistingPackageAsUser} has failed.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED = 3;
-
-    /**
-     * Service-specific error code for {@link #createAndProvisionManagedProfile}:
-     * Indicates the call to {@link #setProfileOwner} returned {@code false}.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4;
-
-    /**
-     * Service-specific error code for {@link #createAndProvisionManagedProfile}:
-     * Indicates that starting the newly created profile has failed.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5;
-
-    /**
-     * Service-specific error code for {@link #provisionFullyManagedDevice}:
-     * Indicates that removing the non required apps have failed.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED = 6;
-
-    /**
-     * Service-specific error code for {@link #provisionFullyManagedDevice}:
-     * Indicates the call to {@link #setDeviceOwner} returned {@code false}.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7;
-
-    /**
-     * Service-specific error codes for {@link #createAndProvisionManagedProfile} and
-     * {@link #provisionFullyManagedDevice} indicating all the errors during provisioning.
-     *
-     * @hide
-     */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "PROVISIONING_RESULT_" }, value = {
-            PROVISIONING_RESULT_PRE_CONDITION_FAILED, PROVISIONING_RESULT_PROFILE_CREATION_FAILED,
-            PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED,
-            PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED,
-            PROVISIONING_RESULT_STARTING_PROFILE_FAILED,
-            PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED,
-            PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED
-    })
-    public @interface ProvisioningResult {}
-
-    /**
      * Disable all configurable SystemUI features during LockTask mode. This includes,
      * <ul>
      *     <li>system info area in the status bar (connectivity icons, clock, etc.)
@@ -2821,6 +2848,43 @@
             "android.app.action.ADMIN_POLICY_COMPLIANCE";
 
     /**
+     * Activity action: Starts the device management role holder updater.
+     *
+     * <p>The activity must handle the device management role holder update and set the intent
+     * result to either {@link Activity#RESULT_OK} if the update was successful, {@link
+     * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR} if it encounters a problem
+     * that may be solved by relaunching it again, or {@link
+     * #RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR} if it encounters a problem
+     * that will not be solved by relaunching it again.
+     *
+     * @hide
+     */
+    // TODO(b/208628038): Uncomment when the permission is in place
+    //  @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_ROLE_HOLDER)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER =
+            "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+
+    /**
+     * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+     * handler if it encounters a problem that may be solved by relaunching it again.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_RECOVERABLE_ERROR = 1;
+
+    /**
+     * Result code that can be returned by the {@link #ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER}
+     * handler if it encounters a problem that will not be solved by relaunching it again.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int RESULT_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER_UNRECOVERABLE_ERROR = 2;
+
+    /**
      * Maximum supported password length. Kind-of arbitrary.
      * @hide
      */
@@ -5675,13 +5739,23 @@
             "android.app.action.CHECK_POLICY_COMPLIANCE";
 
     /**
-     * Broadcast action: notify managed provisioning that new managed user is created.
+     * Broadcast action: notify managed provisioning that PO/DO provisioning has completed.
      *
      * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String ACTION_MANAGED_USER_CREATED =
-            "android.app.action.MANAGED_USER_CREATED";
+    public static final String ACTION_PROVISIONING_COMPLETED =
+            "android.app.action.PROVISIONING_COMPLETED";
+
+    /**
+     * Extra for {@link #ACTION_PROVISIONING_COMPLETED} to indicate the provisioning action that has
+     * been completed, this can either be {@link #ACTION_PROVISION_MANAGED_PROFILE},
+     * {@link #ACTION_PROVISION_MANAGED_DEVICE}, or {@link #ACTION_PROVISION_MANAGED_USER}.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PROVISIONING_ACTION =
+            "android.app.extra.PROVISIONING_ACTION";
 
     /**
      * Broadcast action: notify system that a new (Android) user was added when the device is
@@ -11277,9 +11351,10 @@
      * @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
      * @hide
      */
-    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
     public @ProvisioningPreCondition int checkProvisioningPreCondition(
-            @Nullable String action, @NonNull String packageName) {
+            @NonNull String action, @NonNull String packageName) {
         try {
             return mService.checkProvisioningPreCondition(action, packageName);
         } catch (RemoteException re) {
@@ -11999,8 +12074,10 @@
      *
      * @param state to store
      * @param userHandle for user
+     *
      * @hide
      */
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
     public void setUserProvisioningState(@UserProvisioningState int state, int userHandle) {
         if (mService != null) {
             try {
@@ -12012,6 +12089,34 @@
     }
 
     /**
+     * Set the {@link UserProvisioningState} for the supplied user. The supplied user has to be
+     * manged, otherwise it will throw an {@link IllegalStateException}.
+     *
+     * <p> For managed users/profiles/devices, only the following state changes are allowed:
+     * <ul>
+     *     <li>{@link #STATE_USER_UNMANAGED} can change to any other state except itself
+     *     <li>{@link #STATE_USER_SETUP_INCOMPLETE} and {@link #STATE_USER_SETUP_COMPLETE} can only
+     *     change to {@link #STATE_USER_SETUP_FINALIZED}</li>
+     *     <li>{@link #STATE_USER_PROFILE_COMPLETE} can only change to
+     *     {@link #STATE_USER_PROFILE_FINALIZED}</li>
+     *     <li>{@link #STATE_USER_SETUP_FINALIZED} can't be changed to any other state</li>
+     *     <li>{@link #STATE_USER_PROFILE_FINALIZED} can only change to
+     *     {@link #STATE_USER_UNMANAGED}</li>
+     * </ul>
+     * @param state to store
+     * @param userHandle for user
+     * @throws IllegalStateException if called with an invalid state change.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void setUserProvisioningState(
+            @UserProvisioningState int state, @NonNull UserHandle userHandle) {
+        setUserProvisioningState(state, userHandle.getIdentifier());
+    }
+
+    /**
      * Indicates the entity that controls the device. Two users are
      * affiliated if the set of ids set by the device owner and the admin of the secondary user.
      *
@@ -13949,7 +14054,8 @@
      * @hide
      */
     @Nullable
-    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
     public UserHandle createAndProvisionManagedProfile(
             @NonNull ManagedProfileProvisioningParams provisioningParams)
             throws ProvisioningException {
@@ -13982,7 +14088,7 @@
      *
      * @hide
      */
-    @TestApi
+    @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
     public void provisionFullyManagedDevice(
             @NonNull FullyManagedDeviceProvisioningParams provisioningParams)
diff --git a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
index 80655dd..8c232c0 100644
--- a/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
+++ b/core/java/android/app/admin/FullyManagedDeviceProvisioningParams.java
@@ -21,7 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -32,9 +32,10 @@
 /**
  * Params required to provision a fully managed device, see
  * {@link DevicePolicyManager#provisionFullyManagedDevice}.
+ *
  * @hide
  */
-@TestApi
+@SystemApi
 public final class FullyManagedDeviceProvisioningParams implements Parcelable {
     private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
             "LEAVE_ALL_SYSTEM_APPS_ENABLED";
@@ -92,29 +93,50 @@
         return localeStr == null ? null : Locale.forLanguageTag(localeStr);
     }
 
+    /**
+     * Returns the device owner's {@link ComponentName}.
+     */
     @NonNull
     public ComponentName getDeviceAdminComponentName() {
         return mDeviceAdminComponentName;
     }
 
+    /**
+     * Returns the device owner's name.
+     */
     @NonNull
     public String getOwnerName() {
         return mOwnerName;
     }
 
+    /**
+     * Returns {@code true} if system apps should be left enabled after provisioning.
+     */
     public boolean isLeaveAllSystemAppsEnabled() {
         return mLeaveAllSystemAppsEnabled;
     }
 
+    /**
+     * If set, it returns the time zone to set for the device after provisioning, otherwise returns
+     * {@code null};
+     */
     @Nullable
     public String getTimeZone() {
         return mTimeZone;
     }
 
+    /**
+     * If set, it returns the local time to set for the device after provisioning, otherwise returns
+     * 0.
+     */
     public long getLocalTime() {
         return mLocalTime;
     }
 
+    /**
+     * If set, it returns the {@link Locale} to set for the device after provisioning, otherwise
+     * returns {@code null}.
+     */
     @Nullable
     public @SuppressLint("UseIcu") Locale getLocale() {
         return mLocale;
@@ -130,6 +152,8 @@
 
     /**
      * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+     *
+     * @hide
      */
     public void logParams(@NonNull String callerPackage) {
         requireNonNull(callerPackage);
@@ -232,8 +256,7 @@
          * See {@link DevicePolicyManager#EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT}.
          */
         @NonNull
-        @SuppressLint("MissingGetterMatchingBuilder")
-        public Builder setDeviceOwnerCanGrantSensorsPermissions(boolean mayGrant) {
+        public Builder setCanDeviceOwnerGrantSensorsPermissions(boolean mayGrant) {
             mDeviceOwnerCanGrantSensorsPermissions = mayGrant;
             return this;
         }
@@ -261,6 +284,9 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
     @Override
     public String toString() {
         return "FullyManagedDeviceProvisioningParams{"
diff --git a/core/java/android/app/admin/ManagedProfileProvisioningParams.java b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
index 1a6099a..ccbef73 100644
--- a/core/java/android/app/admin/ManagedProfileProvisioningParams.java
+++ b/core/java/android/app/admin/ManagedProfileProvisioningParams.java
@@ -21,7 +21,7 @@
 import android.accounts.Account;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,7 +33,7 @@
  *
  * @hide
  */
-@TestApi
+@SystemApi
 public final class ManagedProfileProvisioningParams implements Parcelable {
     private static final String LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM =
             "LEAVE_ALL_SYSTEM_APPS_ENABLED";
@@ -48,7 +48,7 @@
     @Nullable private final Account mAccountToMigrate;
     private final boolean mLeaveAllSystemAppsEnabled;
     private final boolean mOrganizationOwnedProvisioning;
-    private final boolean mKeepAccountMigrated;
+    private final boolean mKeepAccountOnMigration;
 
 
     private ManagedProfileProvisioningParams(
@@ -58,50 +58,75 @@
             @Nullable Account accountToMigrate,
             boolean leaveAllSystemAppsEnabled,
             boolean organizationOwnedProvisioning,
-            boolean keepAccountMigrated) {
+            boolean keepAccountOnMigration) {
         this.mProfileAdminComponentName = requireNonNull(profileAdminComponentName);
         this.mOwnerName = requireNonNull(ownerName);
         this.mProfileName = profileName;
         this.mAccountToMigrate = accountToMigrate;
         this.mLeaveAllSystemAppsEnabled = leaveAllSystemAppsEnabled;
         this.mOrganizationOwnedProvisioning = organizationOwnedProvisioning;
-        this.mKeepAccountMigrated = keepAccountMigrated;
+        this.mKeepAccountOnMigration = keepAccountOnMigration;
     }
 
+    /**
+     * Returns the profile owner's {@link ComponentName}.
+     */
     @NonNull
     public ComponentName getProfileAdminComponentName() {
         return mProfileAdminComponentName;
     }
 
+    /**
+     * Returns the profile owner's name.
+     */
     @NonNull
     public String getOwnerName() {
         return mOwnerName;
     }
 
+    /**
+     * Returns the profile's name if set, otherwise returns {@code null}.
+     */
     @Nullable
     public String getProfileName() {
         return mProfileName;
     }
 
+    /**
+     * If set, it returns the {@link Account} to migrate from the parent profile to the managed
+     * profile after provisioning, otherwise returns {@code null}.
+     */
     @Nullable
     public Account getAccountToMigrate() {
         return mAccountToMigrate;
     }
 
+    /**
+     * Returns {@code true} if system apps should be left enabled after provisioning.
+     */
     public boolean isLeaveAllSystemAppsEnabled() {
         return mLeaveAllSystemAppsEnabled;
     }
 
+    /**
+     * Returns {@code true} if this is an org owned device.
+     */
     public boolean isOrganizationOwnedProvisioning() {
         return mOrganizationOwnedProvisioning;
     }
 
-    public boolean isKeepAccountMigrated() {
-        return mKeepAccountMigrated;
+    /**
+     * Returns {@code true} if the migrated account from {@link #getAccountToMigrate()} should be
+     * kept in parent profile.
+     */
+    public boolean isKeepingAccountOnMigration() {
+        return mKeepAccountOnMigration;
     }
 
     /**
      * Logs the provisioning params using {@link DevicePolicyEventLogger}.
+     *
+     * @hide
      */
     public void logParams(@NonNull String callerPackage) {
         requireNonNull(callerPackage);
@@ -109,7 +134,7 @@
         logParam(callerPackage, LEAVE_ALL_SYSTEM_APPS_ENABLED_PARAM, mLeaveAllSystemAppsEnabled);
         logParam(callerPackage, ORGANIZATION_OWNED_PROVISIONING_PARAM,
                 mOrganizationOwnedProvisioning);
-        logParam(callerPackage, KEEP_MIGRATED_ACCOUNT_PARAM, mKeepAccountMigrated);
+        logParam(callerPackage, KEEP_MIGRATED_ACCOUNT_PARAM, mKeepAccountOnMigration);
         logParam(callerPackage, ACCOUNT_TO_MIGRATE_PROVIDED_PARAM,
                 /* value= */ mAccountToMigrate != null);
     }
@@ -134,7 +159,7 @@
         @Nullable private Account mAccountToMigrate;
         private boolean mLeaveAllSystemAppsEnabled;
         private boolean mOrganizationOwnedProvisioning;
-        private boolean mKeepAccountMigrated;
+        private boolean mKeepingAccountOnMigration;
 
         /**
          * Initialize a new {@link Builder) to construct a {@link ManagedProfileProvisioningParams}.
@@ -204,8 +229,8 @@
          * Defaults to {@code false}.
          */
         @NonNull
-        public Builder setKeepAccountMigrated(boolean keepAccountMigrated) {
-            this.mKeepAccountMigrated = keepAccountMigrated;
+        public Builder setKeepingAccountOnMigration(boolean keepingAccountOnMigration) {
+            this.mKeepingAccountOnMigration = keepingAccountOnMigration;
             return this;
         }
 
@@ -223,7 +248,7 @@
                     mAccountToMigrate,
                     mLeaveAllSystemAppsEnabled,
                     mOrganizationOwnedProvisioning,
-                    mKeepAccountMigrated);
+                    mKeepingAccountOnMigration);
         }
     }
 
@@ -232,6 +257,9 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
     @Override
     public String toString() {
         return "ManagedProfileProvisioningParams{"
@@ -241,7 +269,7 @@
                 + ", mAccountToMigrate=" + (mAccountToMigrate == null ? "null" : mAccountToMigrate)
                 + ", mLeaveAllSystemAppsEnabled=" + mLeaveAllSystemAppsEnabled
                 + ", mOrganizationOwnedProvisioning=" + mOrganizationOwnedProvisioning
-                + ", mKeepAccountMigrated=" + mKeepAccountMigrated
+                + ", mKeepAccountOnMigration=" + mKeepAccountOnMigration
                 + '}';
     }
 
@@ -253,7 +281,7 @@
         dest.writeTypedObject(mAccountToMigrate, flags);
         dest.writeBoolean(mLeaveAllSystemAppsEnabled);
         dest.writeBoolean(mOrganizationOwnedProvisioning);
-        dest.writeBoolean(mKeepAccountMigrated);
+        dest.writeBoolean(mKeepAccountOnMigration);
     }
 
     public static final @NonNull Creator<ManagedProfileProvisioningParams> CREATOR =
diff --git a/core/java/android/app/admin/ProvisioningException.java b/core/java/android/app/admin/ProvisioningException.java
index 639859b..d374c16 100644
--- a/core/java/android/app/admin/ProvisioningException.java
+++ b/core/java/android/app/admin/ProvisioningException.java
@@ -15,10 +15,15 @@
  */
 
 package android.app.admin;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.TestApi;
+import android.annotation.SystemApi;
+import android.content.pm.PackageManager;
 import android.util.AndroidException;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Thrown to indicate a failure during {@link DevicePolicyManager#provisionFullyManagedDevice} and
  * {@link DevicePolicyManager#createAndProvisionManagedProfile}.
@@ -26,17 +31,88 @@
  * @hide
  *
  */
-@TestApi
+@SystemApi
 public class ProvisioningException extends AndroidException {
-    private final @DevicePolicyManager.ProvisioningResult int mProvisioningResult;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#provisionFullyManagedDevice} and
+     * {@link DevicePolicyManager#createAndProvisionManagedProfile}:
+     * Indicates a generic failure.
+     */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#provisionFullyManagedDevice} and
+     * {@link DevicePolicyManager#createAndProvisionManagedProfile}:
+     * Indicates the call to {@link DevicePolicyManager#checkProvisioningPreCondition} returned an
+     * error code.
+     */
+    public static final int ERROR_PRE_CONDITION_FAILED = 1;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#createAndProvisionManagedProfile}:
+     * Indicates that the profile creation failed.
+     */
+    public static final int ERROR_PROFILE_CREATION_FAILED = 2;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#createAndProvisionManagedProfile}:
+     * Indicates the call to {@link PackageManager#installExistingPackageAsUser} has failed.
+     */
+    public static final int ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED = 3;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#createAndProvisionManagedProfile}:
+     * Indicates that setting the profile owner failed.
+     */
+    public static final int ERROR_SETTING_PROFILE_OWNER_FAILED = 4;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#createAndProvisionManagedProfile}:
+     * Indicates that starting the newly created profile has failed.
+     */
+    public static final int ERROR_STARTING_PROFILE_FAILED = 5;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#provisionFullyManagedDevice}:
+     * Indicates that removing the non required apps have failed.
+     */
+    public static final int ERROR_REMOVE_NON_REQUIRED_APPS_FAILED = 6;
+
+    /**
+     * Service-specific error code for {@link DevicePolicyManager#provisionFullyManagedDevice}:
+     * Indicates that setting the device owner failed.
+     */
+    public static final int ERROR_SET_DEVICE_OWNER_FAILED = 7;
+
+    /**
+     * Service-specific error codes for {@link DevicePolicyManager#createAndProvisionManagedProfile}
+     * and {@link DevicePolicyManager#provisionFullyManagedDevice} indicating all the errors
+     * during provisioning.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "ERROR_" }, value = {
+            ERROR_UNKNOWN, ERROR_PRE_CONDITION_FAILED,
+            ERROR_PROFILE_CREATION_FAILED,
+            ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED,
+            ERROR_SETTING_PROFILE_OWNER_FAILED,
+            ERROR_STARTING_PROFILE_FAILED,
+            ERROR_REMOVE_NON_REQUIRED_APPS_FAILED,
+            ERROR_SET_DEVICE_OWNER_FAILED
+    })
+    public @interface ProvisioningError {}
+
+    private final @ProvisioningError int mProvisioningError;
 
     public ProvisioningException(@NonNull Exception cause,
-            @DevicePolicyManager.ProvisioningResult int provisioningResult) {
+            @ProvisioningError int provisioningError) {
         super(cause);
-        mProvisioningResult = provisioningResult;
+        mProvisioningError = provisioningError;
     }
 
-    public @DevicePolicyManager.ProvisioningResult int getProvisioningResult() {
-        return mProvisioningResult;
+    public @ProvisioningError int getProvisioningError() {
+        return mProvisioningError;
     }
 }
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index e1f6af0..6e49e95 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -815,13 +815,13 @@
                     mAutofillHints = in.readStringArray();
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_VALUE) != 0) {
-                    mAutofillValue = in.readParcelable(null);
+                    mAutofillValue = in.readParcelable(null, android.view.autofill.AutofillValue.class);
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
                     mAutofillOptions = in.readCharSequenceArray();
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_HTML_INFO) != 0) {
-                    mHtmlInfo = in.readParcelable(null);
+                    mHtmlInfo = in.readParcelable(null, android.view.ViewStructure.HtmlInfo.class);
                 }
                 if ((autofillFlags & AUTOFILL_FLAGS_HAS_MIN_TEXT_EMS) != 0) {
                     mMinEms = in.readInt();
@@ -886,7 +886,7 @@
                 mWebDomain = in.readString();
             }
             if ((flags&FLAGS_HAS_LOCALE_LIST) != 0) {
-                mLocaleList = in.readParcelable(null);
+                mLocaleList = in.readParcelable(null, android.os.LocaleList.class);
             }
             if ((flags & FLAGS_HAS_MIME_TYPES) != 0) {
                 mReceiveContentMimeTypes = in.readStringArray();
diff --git a/core/java/android/app/cloudsearch/OWNERS b/core/java/android/app/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/core/java/android/app/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/app/communal/CommunalManager.java b/core/java/android/app/communal/CommunalManager.java
index 60730ad..22f07693 100644
--- a/core/java/android/app/communal/CommunalManager.java
+++ b/core/java/android/app/communal/CommunalManager.java
@@ -17,15 +17,21 @@
 package android.app.communal;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.Overridable;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import java.util.concurrent.Executor;
 
 /**
  * System private class for talking with the
@@ -33,10 +39,12 @@
  *
  * @hide
  */
-@SystemService(Context.COMMUNAL_MANAGER_SERVICE)
+@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
+@SystemService(Context.COMMUNAL_SERVICE)
 @RequiresFeature(PackageManager.FEATURE_COMMUNAL_MODE)
 public final class CommunalManager {
     private final ICommunalManager mService;
+    private final ArrayMap<CommunalModeListener, ICommunalModeListener> mCommunalModeListeners;
 
     /**
      * This change id is used to annotate packages which can run in communal mode by default,
@@ -59,15 +67,20 @@
     @Disabled
     public static final long ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT = 200324021L;
 
+    /** @hide */
     public CommunalManager(ICommunalManager service) {
         mService = service;
+        mCommunalModeListeners = new ArrayMap<CommunalModeListener, ICommunalModeListener>();
     }
 
     /**
      * Updates whether or not the communal view is currently showing over the lockscreen.
      *
      * @param isShowing Whether communal view is showing.
+     *
+     * @hide
      */
+    @TestApi
     @RequiresPermission(Manifest.permission.WRITE_COMMUNAL_STATE)
     public void setCommunalViewShowing(boolean isShowing) {
         try {
@@ -76,4 +89,72 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Checks whether or not the communal view is currently showing over the lockscreen.
+     */
+    @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+    public boolean isCommunalMode() {
+        try {
+            return mService.isCommunalMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Listener for communal state changes.
+     */
+    @FunctionalInterface
+    public interface CommunalModeListener {
+        /**
+         * Callback function that executes when the communal state changes.
+         */
+        void onCommunalModeChanged(boolean isCommunalMode);
+    }
+
+    /**
+     * Registers a callback to execute when the communal state changes.
+     *
+     * @param listener The listener to add to receive communal state changes.
+     * @param executor {@link Executor} to dispatch to. To dispatch the callback to the main
+     *                 thread of your application, use
+     *                 {@link android.content.Context#getMainExecutor()}.
+     */
+    @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+    public void addCommunalModeListener(@NonNull Executor executor,
+            @NonNull CommunalModeListener listener) {
+        synchronized (mCommunalModeListeners) {
+            try {
+                ICommunalModeListener iListener = new ICommunalModeListener.Stub() {
+                    @Override
+                    public void onCommunalModeChanged(boolean isCommunalMode) {
+                        executor.execute(() -> listener.onCommunalModeChanged(isCommunalMode));
+                    }
+                };
+                mService.addCommunalModeListener(iListener);
+                mCommunalModeListeners.put(listener, iListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters a callback that executes when communal state changes.
+     */
+    @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+    public void removeCommunalModeListener(@NonNull CommunalModeListener listener) {
+        synchronized (mCommunalModeListeners) {
+            ICommunalModeListener iListener = mCommunalModeListeners.get(listener);
+            if (iListener != null) {
+                try {
+                    mService.removeCommunalModeListener(iListener);
+                    mCommunalModeListeners.remove(listener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/communal/ICommunalManager.aidl b/core/java/android/app/communal/ICommunalManager.aidl
index 02e8a65..869891e 100644
--- a/core/java/android/app/communal/ICommunalManager.aidl
+++ b/core/java/android/app/communal/ICommunalManager.aidl
@@ -16,12 +16,17 @@
 
 package android.app.communal;
 
+import android.app.communal.ICommunalModeListener;
+
 /**
  * System private API for talking with the communal manager service that handles communal mode
  * state.
  *
  * @hide
  */
-oneway interface ICommunalManager {
-    void setCommunalViewShowing(boolean isShowing);
+interface ICommunalManager {
+    oneway void setCommunalViewShowing(boolean isShowing);
+    boolean isCommunalMode();
+    void addCommunalModeListener(in ICommunalModeListener listener);
+    void removeCommunalModeListener(in ICommunalModeListener listener);
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/core/java/android/app/communal/ICommunalModeListener.aidl
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
rename to core/java/android/app/communal/ICommunalModeListener.aidl
index fbb78c8..006e782 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/core/java/android/app/communal/ICommunalModeListener.aidl
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.flags;
+package android.app.communal;
 
 /**
- * Class to manage simple DeviceConfig-based feature flags.
+ * System private API to be notified about communal mode changes.
  *
- * See {@link Flags} for instructions on defining new flags.
+ * @hide
  */
-public interface FeatureFlags extends FlagReader {
-}
+oneway interface ICommunalModeListener {
+    void onCommunalModeChanged(boolean isCommunalMode);
+}
\ No newline at end of file
diff --git a/core/java/android/app/people/ConversationChannel.java b/core/java/android/app/people/ConversationChannel.java
index 2bf71b0..ab350f2 100644
--- a/core/java/android/app/people/ConversationChannel.java
+++ b/core/java/android/app/people/ConversationChannel.java
@@ -83,16 +83,16 @@
     }
 
     public ConversationChannel(Parcel in) {
-        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
         mUid = in.readInt();
-        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mNotificationChannelGroup =
-                in.readParcelable(NotificationChannelGroup.class.getClassLoader());
+                in.readParcelable(NotificationChannelGroup.class.getClassLoader(), android.app.NotificationChannelGroup.class);
         mLastEventTimestamp = in.readLong();
         mHasActiveNotifications = in.readBoolean();
         mHasBirthdayToday = in.readBoolean();
         mStatuses = new ArrayList<>();
-        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
+        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class);
     }
 
     @Override
diff --git a/core/java/android/app/people/ConversationStatus.java b/core/java/android/app/people/ConversationStatus.java
index 8038158..a7b61b3 100644
--- a/core/java/android/app/people/ConversationStatus.java
+++ b/core/java/android/app/people/ConversationStatus.java
@@ -126,7 +126,7 @@
         mActivity = p.readInt();
         mAvailability = p.readInt();
         mDescription = p.readCharSequence();
-        mIcon = p.readParcelable(Icon.class.getClassLoader());
+        mIcon = p.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
         mStartTimeMs = p.readLong();
         mEndTimeMs = p.readLong();
     }
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index e11861f..4337111 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -472,9 +472,9 @@
     public PeopleSpaceTile(Parcel in) {
         mId = in.readString();
         mUserName = in.readCharSequence();
-        mUserIcon = in.readParcelable(Icon.class.getClassLoader());
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
-        mUserHandle = in.readParcelable(UserHandle.class.getClassLoader());
+        mUserIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mUserHandle = in.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
         mPackageName = in.readString();
         mBirthdayText = in.readString();
         mLastInteractionTimestamp = in.readLong();
@@ -483,12 +483,12 @@
         mNotificationContent = in.readCharSequence();
         mNotificationSender = in.readCharSequence();
         mNotificationCategory = in.readString();
-        mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader());
+        mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mMessagesCount = in.readInt();
-        mIntent = in.readParcelable(Intent.class.getClassLoader());
+        mIntent = in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class);
         mNotificationTimestamp = in.readLong();
         mStatuses = new ArrayList<>();
-        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
+        in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader(), android.app.people.ConversationStatus.class);
         mCanBypassDnd = in.readBoolean();
         mIsPackageSuspended = in.readBoolean();
         mIsUserQuieted = in.readBoolean();
diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java
index 963e750..51e3953 100644
--- a/core/java/android/app/prediction/AppTargetEvent.java
+++ b/core/java/android/app/prediction/AppTargetEvent.java
@@ -72,7 +72,7 @@
     }
 
     private AppTargetEvent(Parcel parcel) {
-        mTarget = parcel.readParcelable(null);
+        mTarget = parcel.readParcelable(null, android.app.prediction.AppTarget.class);
         mLocation = parcel.readString();
         mAction = parcel.readInt();
     }
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index fbb37db..30a6c31 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -197,11 +197,11 @@
         if (readActivityToken) {
             mActivityToken = in.readStrongBinder();
         }
-        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
+        mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
         final boolean readActivityCallbacks = in.readBoolean();
         if (readActivityCallbacks) {
             mActivityCallbacks = new ArrayList<>();
-            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
+            in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
         }
     }
 
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 0589f4a..fb8a83b 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -299,7 +299,7 @@
      * @see #getCallingPackage()
      */
     public @NonNull PendingIntent onCreatePermissionRequest(Uri sliceUri) {
-        return createPermissionIntent(getContext(), sliceUri, getCallingPackage());
+        return createPermissionPendingIntent(getContext(), sliceUri, getCallingPackage());
     }
 
     @Override
@@ -508,7 +508,17 @@
     /**
      * @hide
      */
-    public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
+    public static PendingIntent createPermissionPendingIntent(Context context, Uri sliceUri,
+            String callingPackage) {
+        return PendingIntent.getActivity(context, 0,
+                createPermissionIntent(context, sliceUri, callingPackage),
+                PendingIntent.FLAG_IMMUTABLE);
+    }
+
+    /**
+     * @hide
+     */
+    public static Intent createPermissionIntent(Context context, Uri sliceUri,
             String callingPackage) {
         Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
         intent.setComponent(ComponentName.unflattenFromString(context.getResources().getString(
@@ -518,8 +528,7 @@
         // Unique pending intent.
         intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
                 .build());
-
-        return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+        return intent;
     }
 
     /**
diff --git a/core/java/android/app/smartspace/SmartspaceTargetEvent.java b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
index 61f8723..89caab7 100644
--- a/core/java/android/app/smartspace/SmartspaceTargetEvent.java
+++ b/core/java/android/app/smartspace/SmartspaceTargetEvent.java
@@ -96,7 +96,7 @@
     }
 
     private SmartspaceTargetEvent(Parcel parcel) {
-        mSmartspaceTarget = parcel.readParcelable(null);
+        mSmartspaceTarget = parcel.readParcelable(null, android.app.smartspace.SmartspaceTarget.class);
         mSmartspaceActionId = parcel.readString();
         mEventType = parcel.readInt();
     }
diff --git a/core/java/android/app/time/ExternalTimeSuggestion.java b/core/java/android/app/time/ExternalTimeSuggestion.java
index 8e281c0..0f98b44 100644
--- a/core/java/android/app/time/ExternalTimeSuggestion.java
+++ b/core/java/android/app/time/ExternalTimeSuggestion.java
@@ -101,11 +101,11 @@
     }
 
     private static ExternalTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         ExternalTimeSuggestion suggestion =
                 new ExternalTimeSuggestion(utcTime.getReferenceTimeMillis(), utcTime.getValue());
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/time/TimeCapabilitiesAndConfig.java b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
index 4a10447..71fce14 100644
--- a/core/java/android/app/time/TimeCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeCapabilitiesAndConfig.java
@@ -59,8 +59,8 @@
 
     @NonNull
     private static TimeCapabilitiesAndConfig readFromParcel(Parcel in) {
-        TimeCapabilities capabilities = in.readParcelable(null);
-        TimeConfiguration configuration = in.readParcelable(null);
+        TimeCapabilities capabilities = in.readParcelable(null, android.app.time.TimeCapabilities.class);
+        TimeConfiguration configuration = in.readParcelable(null, android.app.time.TimeConfiguration.class);
         return new TimeCapabilitiesAndConfig(capabilities, configuration);
     }
 
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index a9ea76f..cd91b04 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -61,8 +61,8 @@
 
     @NonNull
     private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
-        TimeZoneCapabilities capabilities = in.readParcelable(null);
-        TimeZoneConfiguration configuration = in.readParcelable(null);
+        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);
     }
 
diff --git a/core/java/android/app/timedetector/GnssTimeSuggestion.java b/core/java/android/app/timedetector/GnssTimeSuggestion.java
index 6478a2d..8ccff62 100644
--- a/core/java/android/app/timedetector/GnssTimeSuggestion.java
+++ b/core/java/android/app/timedetector/GnssTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static GnssTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         GnssTimeSuggestion suggestion = new GnssTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 299e951..1699a5f 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static ManualTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         ManualTimeSuggestion suggestion = new ManualTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
index a5259c2..2030083 100644
--- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -66,10 +66,10 @@
     }
 
     private static NetworkTimeSuggestion createFromParcel(Parcel in) {
-        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+        TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class);
         NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(utcTime);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
index 6c3a304..52d0bbe 100644
--- a/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
+++ b/core/java/android/app/timedetector/TelephonyTimeSuggestion.java
@@ -77,10 +77,10 @@
     private static TelephonyTimeSuggestion createFromParcel(Parcel in) {
         int slotIndex = in.readInt();
         TelephonyTimeSuggestion suggestion = new TelephonyTimeSuggestion.Builder(slotIndex)
-                .setUtcTime(in.readParcelable(null /* classLoader */))
+                .setUtcTime(in.readParcelable(null /* classLoader */, android.os.TimestampedValue.class))
                 .build();
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
diff --git a/core/java/android/app/timezone/RulesState.java b/core/java/android/app/timezone/RulesState.java
index ee88ec54..516ad03 100644
--- a/core/java/android/app/timezone/RulesState.java
+++ b/core/java/android/app/timezone/RulesState.java
@@ -195,12 +195,12 @@
 
     private static RulesState createFromParcel(Parcel in) {
         String baseRulesVersion = in.readString();
-        DistroFormatVersion distroFormatVersionSupported = in.readParcelable(null);
+        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);
+        DistroRulesVersion stagedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
         int installedDistroStatus = in.readByte();
-        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null);
+        DistroRulesVersion installedDistroRulesVersion = in.readParcelable(null, android.app.timezone.DistroRulesVersion.class);
         return new RulesState(baseRulesVersion, distroFormatVersionSupported, operationInProgress,
                 distroStagedState, stagedDistroRulesVersion,
                 installedDistroStatus, installedDistroRulesVersion);
diff --git a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
index 01a60b1..387319e 100644
--- a/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/ManualTimeZoneSuggestion.java
@@ -65,7 +65,7 @@
         String zoneId = in.readString();
         ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(zoneId);
         @SuppressWarnings("unchecked")
-        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+        ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
         suggestion.mDebugInfo = debugInfo;
         return suggestion;
     }
diff --git a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
index eb6750f..e5b4e46 100644
--- a/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/TelephonyTimeZoneSuggestion.java
@@ -165,7 +165,7 @@
                 .setQuality(in.readInt())
                 .build();
         List<String> debugInfo =
-                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader());
+                in.readArrayList(TelephonyTimeZoneSuggestion.class.getClassLoader(), java.lang.String.class);
         if (debugInfo != null) {
             suggestion.addDebugInfo(debugInfo);
         }
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index ceab02f..aac23d8 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -103,6 +103,14 @@
     String SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK = "enable_telephony_fallback";
 
     /**
+     * A shell command that dumps a {@link
+     * com.android.server.timezonedetector.MetricsTimeZoneDetectorState} object to stdout for
+     * debugging.
+     * @hide
+     */
+    String SHELL_COMMAND_DUMP_METRICS = "dump_metrics";
+
+    /**
      * A shared utility method to create a {@link ManualTimeZoneSuggestion}.
      *
      * @hide
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index 0ccb058..ba6bcdc 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -148,7 +148,7 @@
                     return builder.setVolumeUuid(in.readString())
                             .setUid(in.readInt())
                             .setQuota(in.readLong())
-                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader()))
+                            .setUsageStats(in.readParcelable(UsageStats.class.getClassLoader(), android.app.usage.UsageStats.class))
                             .build();
                 }
 
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 3cbb24b..630b8d2 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -779,24 +779,25 @@
      * thinks the app will be launched sooner, 2) the estimated time has passed. Passing in
      * {@link Long#MAX_VALUE} effectively clears the previously set launch time for the app.
      *
-     * @param packageName         The package name of the app to set the bucket for.
-     * @param estimatedLaunchTime The next time the app is expected to be launched. Units are in
-     *                            milliseconds since epoch (the same as
-     *                            {@link System#currentTimeMillis()}).
+     * @param packageName               The package name of the app to set the bucket for.
+     * @param estimatedLaunchTimeMillis The next time the app is expected to be launched. Units are
+     *                                  in milliseconds since epoch (the same as
+     *                                  {@link System#currentTimeMillis()}).
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
-    public void setEstimatedLaunchTime(@NonNull String packageName,
-            @CurrentTimeMillisLong long estimatedLaunchTime) {
+    public void setEstimatedLaunchTimeMillis(@NonNull String packageName,
+            @CurrentTimeMillisLong long estimatedLaunchTimeMillis) {
         if (packageName == null) {
             throw new NullPointerException("package name cannot be null");
         }
-        if (estimatedLaunchTime <= 0) {
+        if (estimatedLaunchTimeMillis <= 0) {
             throw new IllegalArgumentException("estimated launch time must be positive");
         }
         try {
-            mService.setEstimatedLaunchTime(packageName, estimatedLaunchTime, mContext.getUserId());
+            mService.setEstimatedLaunchTime(
+                    packageName, estimatedLaunchTimeMillis, mContext.getUserId());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -806,19 +807,20 @@
      * Changes the estimated launch times for multiple apps at once. The map is keyed by the
      * package name and the value is the estimated launch time.
      *
-     * @param estimatedLaunchTimes A map of package name to estimated launch time.
-     * @see #setEstimatedLaunchTime(String, long)
+     * @param estimatedLaunchTimesMillis A map of package name to estimated launch time.
+     * @see #setEstimatedLaunchTimeMillis(String, long)
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE)
-    public void setEstimatedLaunchTimes(@NonNull Map<String, Long> estimatedLaunchTimes) {
-        if (estimatedLaunchTimes == null) {
-            throw new NullPointerException("estimatedLaunchTimes cannot be null");
+    public void setEstimatedLaunchTimesMillis(
+            @NonNull Map<String, Long> estimatedLaunchTimesMillis) {
+        if (estimatedLaunchTimesMillis == null) {
+            throw new NullPointerException("estimatedLaunchTimesMillis cannot be null");
         }
         final List<AppLaunchEstimateInfo> estimateList =
-                new ArrayList<>(estimatedLaunchTimes.size());
-        for (Map.Entry<String, Long> estimateEntry : estimatedLaunchTimes.entrySet()) {
+                new ArrayList<>(estimatedLaunchTimesMillis.size());
+        for (Map.Entry<String, Long> estimateEntry : estimatedLaunchTimesMillis.entrySet()) {
             final String pkgName = estimateEntry.getKey();
             if (pkgName == null) {
                 throw new NullPointerException("package name cannot be null");
diff --git a/core/java/android/bluetooth/Attributable.java b/core/java/android/bluetooth/Attributable.java
new file mode 100644
index 0000000..d9acbe3
--- /dev/null
+++ b/core/java/android/bluetooth/Attributable.java
@@ -0,0 +1,55 @@
+/*
+ * 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.bluetooth;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.AttributionSource;
+
+import java.util.List;
+
+/**
+ * Marker interface for a class which can have an {@link AttributionSource}
+ * assigned to it; these are typically {@link android.os.Parcelable} classes
+ * which need to be updated after crossing Binder transaction boundaries.
+ *
+ * @hide
+ */
+public interface Attributable {
+    void setAttributionSource(@NonNull AttributionSource attributionSource);
+
+    static @Nullable <T extends Attributable> T setAttributionSource(
+            @Nullable T attributable,
+            @NonNull AttributionSource attributionSource) {
+        if (attributable != null) {
+            attributable.setAttributionSource(attributionSource);
+        }
+        return attributable;
+    }
+
+    static @Nullable <T extends Attributable> List<T> setAttributionSource(
+            @Nullable List<T> attributableList,
+            @NonNull AttributionSource attributionSource) {
+        if (attributableList != null) {
+            final int size = attributableList.size();
+            for (int i = 0; i < size; i++) {
+                setAttributionSource(attributableList.get(i), attributionSource);
+            }
+        }
+        return attributableList;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 1dd32fe..8b9cec1 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -16,7 +16,8 @@
 
 package android.bluetooth;
 
-import android.Manifest;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -29,20 +30,21 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 
 /**
@@ -273,7 +275,7 @@
                     IBluetoothA2dp.class.getName()) {
                 @Override
                 public IBluetoothA2dp getServiceInterface(IBinder service) {
-                    return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothA2dp.Stub.asInterface(service);
                 }
     };
 
@@ -324,17 +326,21 @@
     @UnsupportedAppUsage
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.connect(device);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -366,17 +372,21 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -387,19 +397,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevicesWithAttribution(mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -410,20 +425,25 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStatesWithAttribution(states,
+                        mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStatesWithAttribution(states,
-                                mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -434,18 +454,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BtProfileState int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionState(device);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.STATE_DISCONNECTED;
         }
+        return defaultValue;
     }
 
     /**
@@ -473,18 +496,21 @@
     @UnsupportedAppUsage(trackingBug = 171933273)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && ((device == null) || isValidDevice(device))) {
-                return service.setActiveDevice(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -501,18 +527,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getActiveDevice() {
         if (VDBG) log("getActiveDevice()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        final BluetoothDevice defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevice(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getActiveDevice(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return null;
         }
+        return defaultValue;
     }
 
     /**
@@ -557,22 +589,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -591,19 +624,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return BluetoothAdapter.connectionPolicyToPriority(
-                        service.getPriority(device, mAttributionSource));
-            }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.PRIORITY_OFF;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.PRIORITY_OFF;
-        }
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
     }
 
     /**
@@ -625,18 +646,21 @@
     })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
     /**
@@ -648,17 +672,21 @@
     @RequiresNoPermission
     public boolean isAvrcpAbsoluteVolumeSupported() {
         if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.isAvrcpAbsoluteVolumeSupported();
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isAvrcpAbsoluteVolumeSupported(recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -671,14 +699,16 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setAvrcpAbsoluteVolume(int volume) {
         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
                 service.setAvrcpAbsoluteVolume(volume, mAttributionSource);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
         }
     }
 
@@ -691,18 +721,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isA2dpPlaying(BluetoothDevice device) {
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.isA2dpPlaying(device, mAttributionSource);
+        if (DBG) log("isA2dpPlaying(" + device + ")");
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isA2dpPlaying(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -731,8 +765,7 @@
     /**
      * Gets the current codec status (configuration and capability).
      *
-     * @param device the remote Bluetooth device. If null, use the current
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @return the current codec status
      * @hide
      */
@@ -744,26 +777,28 @@
     public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
         verifyDeviceNotNull(device, "getCodecStatus");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.getCodecStatus(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final BluetoothCodecStatus defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<BluetoothCodecStatus> recv =
+                        new SynchronousResultReceiver();
+                service.getCodecStatus(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
-            return null;
         }
+        return defaultValue;
     }
 
     /**
      * Sets the codec configuration preference.
      *
-     * @param device the remote Bluetooth device. If null, use the current
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @param codecConfig the codec configuration preference
      * @hide
      */
@@ -779,24 +814,23 @@
             Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
             throw new IllegalArgumentException("codecConfig cannot be null");
         }
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
                 service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
-            return;
         }
     }
 
     /**
      * Enables the optional codecs.
      *
-     * @param device the remote Bluetooth device. If null, use the currect
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -812,8 +846,7 @@
     /**
      * Disables the optional codecs.
      *
-     * @param device the remote Bluetooth device. If null, use the currect
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -829,26 +862,25 @@
     /**
      * Enables or disables the optional codecs.
      *
-     * @param device the remote Bluetooth device. If null, use the currect
-     * active A2DP Bluetooth device.
+     * @param device the remote Bluetooth device.
      * @param enable if true, enable the optional codecs, other disable them
      */
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
                 if (enable) {
                     service.enableOptionalCodecs(device, mAttributionSource);
                 } else {
                     service.disableOptionalCodecs(device, mAttributionSource);
                 }
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
-            return;
         }
     }
 
@@ -866,18 +898,23 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @OptionalCodecsSupportStatus
     public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
+        if (DBG) log("isOptionalCodecsSupported(" + device + ")");
         verifyDeviceNotNull(device, "isOptionalCodecsSupported");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.supportsOptionalCodecs(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.supportsOptionalCodecs(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e);
-            return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
         }
+        return defaultValue;
     }
 
     /**
@@ -894,18 +931,23 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     @OptionalCodecsPreferenceStatus
     public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
+        if (DBG) log("isOptionalCodecsEnabled(" + device + ")");
         verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.getOptionalCodecsEnabled(device, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = OPTIONAL_CODECS_PREF_UNKNOWN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getOptionalCodecsEnabled(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return OPTIONAL_CODECS_PREF_UNKNOWN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e);
-            return OPTIONAL_CODECS_PREF_UNKNOWN;
         }
+        return defaultValue;
     }
 
     /**
@@ -923,24 +965,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
             @OptionalCodecsPreferenceStatus int value) {
+        if (DBG) log("setOptionalCodecsEnabled(" + device + ")");
         verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
-        try {
-            if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
-                    && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
-                    && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
-                Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
-                return;
-            }
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
+        if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+                && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+                && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+            Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
+            return;
+        }
+        final IBluetoothA2dp service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
                 service.setOptionalCodecsEnabled(device, value, mAttributionSource);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return;
         }
     }
 
@@ -963,17 +1005,21 @@
     })
     public @Type int getDynamicBufferSupport() {
         if (VDBG) log("getDynamicBufferSupport()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.getDynamicBufferSupport(mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final int defaultValue = DYNAMIC_BUFFER_SUPPORT_NONE;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getDynamicBufferSupport(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return DYNAMIC_BUFFER_SUPPORT_NONE;
-        } catch (RemoteException e) {
-            Log.e(TAG, "failed to get getDynamicBufferSupport, error: ", e);
-            return DYNAMIC_BUFFER_SUPPORT_NONE;
         }
+        return defaultValue;
     }
 
     /**
@@ -994,17 +1040,22 @@
     })
     public @Nullable BufferConstraints getBufferConstraints() {
         if (VDBG) log("getBufferConstraints()");
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.getBufferConstraints(mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final BufferConstraints defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BufferConstraints> recv =
+                        new SynchronousResultReceiver();
+                service.getBufferConstraints(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return null;
         }
+        return defaultValue;
     }
 
     /**
@@ -1029,17 +1080,21 @@
             Log.e(TAG, "Trying to set audio buffer length to a negative value: " + value);
             return false;
         }
-        try {
-            final IBluetoothA2dp service = getService();
-            if (service != null && isEnabled()) {
-                return service.setBufferLengthMillis(codec, value, mAttributionSource);
+        final IBluetoothA2dp service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setBufferLengthMillis(codec, value, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-            return false;
         }
+        return defaultValue;
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 2dd63a0..5941681 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -16,28 +16,31 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.SdkConstant.SdkConstantType;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth A2DP Sink
@@ -87,7 +90,7 @@
                     "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) {
                 @Override
                 public IBluetoothA2dpSink getServiceInterface(IBinder service) {
-                    return IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothA2dpSink.Stub.asInterface(service);
                 }
     };
 
@@ -141,16 +144,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -182,16 +189,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -205,17 +216,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -229,18 +246,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -252,18 +274,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
-        if (VDBG) log("getState(" + device + ")");
+        if (VDBG) log("getConnectionState(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -283,16 +309,21 @@
     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         if (VDBG) log("getAudioConfig(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final BluetoothAudioConfig defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getAudioConfig(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return null;
+                final SynchronousResultReceiver<BluetoothAudioConfig> recv =
+                        new SynchronousResultReceiver();
+                service.getAudioConfig(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -338,20 +369,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -394,16 +427,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -421,17 +458,22 @@
             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
     })
     public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
+        if (VDBG) log("isAudioPlaying(" + device + ")");
         final IBluetoothA2dpSink service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isA2dpPlaying(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isA2dpPlaying(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 20122fb..2d1ecfb 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -29,7 +29,6 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.app.ActivityThread;
 import android.app.PropertyInvalidatedCache;
 import android.bluetooth.BluetoothDevice.Transport;
 import android.bluetooth.BluetoothProfile.ConnectionPolicy;
@@ -48,7 +47,6 @@
 import android.bluetooth.le.ScanResult;
 import android.bluetooth.le.ScanSettings;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.os.Binder;
@@ -58,7 +56,7 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
-import android.os.SystemProperties;
+import android.sysprop.BluetoothProperties;
 import android.util.Log;
 import android.util.Pair;
 
@@ -799,7 +797,7 @@
     @RequiresNoPermission
     public static synchronized BluetoothAdapter getDefaultAdapter() {
         if (sAdapter == null) {
-            sAdapter = createAdapter(BluetoothManager.resolveAttributionSource(null));
+            sAdapter = createAdapter(AttributionSource.myAttributionSource());
         }
         return sAdapter;
     }
@@ -1002,7 +1000,6 @@
         if (!isBleScanAlwaysAvailable()) {
             return false;
         }
-        String packageName = ActivityThread.currentPackageName();
         try {
             return mManagerService.disableBle(mAttributionSource, mToken);
         } catch (RemoteException e) {
@@ -1049,7 +1046,6 @@
         if (!isBleScanAlwaysAvailable()) {
             return false;
         }
-        String packageName = ActivityThread.currentPackageName();
         try {
             return mManagerService.enableBle(mAttributionSource, mToken);
         } catch (RemoteException e) {
@@ -1352,7 +1348,7 @@
                 return true;
             }
             Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later");
-            SystemProperties.set("persist.bluetooth.factoryreset", "true");
+            BluetoothProperties.factory_reset(true);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         } finally {
@@ -2261,21 +2257,21 @@
     public @interface LeFeatureReturnValues {}
 
     /**
-     * Returns {@link BluetoothStatusCodes#SUCCESS} if LE Connected Isochronous Stream Central
-     * feature is supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if
+     * Returns {@link BluetoothStatusCodes#SUCCESS} if the LE audio feature is
+     * supported, returns {@link BluetoothStatusCodes#ERROR_FEATURE_NOT_SUPPORTED} if
      * the feature is not supported or an error code.
      *
-     * @return whether the chipset supports the LE Connected Isochronous Stream Central feature
+     * @return whether the LE audio is supported
      */
     @RequiresNoPermission
-    public @LeFeatureReturnValues int isCisCentralSupported() {
+    public @LeFeatureReturnValues int isLeAudioSupported() {
         if (!getLeAccess()) {
             return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
         }
         try {
             mServiceLock.readLock().lock();
             if (mService != null) {
-                return mService.isCisCentralSupported();
+                return mService.isLeAudioSupported();
             }
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index d27c276..81fc3e1 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -16,22 +16,24 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
@@ -95,8 +97,7 @@
                     "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) {
                 @Override
                 public IBluetoothAvrcpController getServiceInterface(IBinder service) {
-                    return IBluetoothAvrcpController.Stub.asInterface(
-                            Binder.allowBlocking(service));
+                    return IBluetoothAvrcpController.Stub.asInterface(service);
                 }
     };
 
@@ -132,19 +133,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -155,20 +161,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -179,18 +189,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothAvrcpController service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -203,17 +216,22 @@
     public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getPlayerSettings");
         BluetoothAvrcpPlayerSettings settings = null;
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final BluetoothAvrcpPlayerSettings defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                settings = service.getPlayerSettings(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
-                return null;
+                final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv =
+                        new SynchronousResultReceiver();
+                service.getPlayerSettings(device, mAttributionSource, recv);
+                settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return settings;
+        return defaultValue;
     }
 
     /**
@@ -224,18 +242,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
         if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.setPlayerApplicationSetting(plAppSetting, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -247,18 +268,20 @@
     public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
         Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
                 + keyState);
-        final IBluetoothAvrcpController service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothAvrcpController service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource);
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                 return;
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
-                return;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
index 1d0bf97..9a4151a 100644
--- a/core/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -29,16 +29,14 @@
 
 /**
  * Represents the codec configuration for a Bluetooth A2DP source device.
+ * <p>Contains the source codec type, the codec priority, the codec sample
+ * rate, the codec bits per sample, and the codec channel mode.
+ * <p>The source codec type values are the same as those supported by the
+ * device hardware.
  *
  * {@see BluetoothA2dp}
- *
- * {@hide}
  */
 public final class BluetoothCodecConfig implements Parcelable {
-    // Add an entry for each source codec here.
-    // NOTE: The values should be same as those listed in the following file:
-    //   hardware/libhardware/include/hardware/bt_av.h
-
     /** @hide */
     @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {
             SOURCE_CODEC_TYPE_SBC,
@@ -46,33 +44,49 @@
             SOURCE_CODEC_TYPE_APTX,
             SOURCE_CODEC_TYPE_APTX_HD,
             SOURCE_CODEC_TYPE_LDAC,
-            SOURCE_CODEC_TYPE_MAX,
             SOURCE_CODEC_TYPE_INVALID
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SourceCodecType {}
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type SBC. This is the mandatory source codec
+     * type.
+     */
     public static final int SOURCE_CODEC_TYPE_SBC = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type AAC.
+     */
     public static final int SOURCE_CODEC_TYPE_AAC = 1;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type APTX.
+     */
     public static final int SOURCE_CODEC_TYPE_APTX = 2;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type APTX HD.
+     */
     public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
 
-    @UnsupportedAppUsage
+    /**
+     * Source codec type LDAC.
+     */
     public static final int SOURCE_CODEC_TYPE_LDAC = 4;
 
-    @UnsupportedAppUsage
-    public static final int SOURCE_CODEC_TYPE_MAX = 5;
-
-    @UnsupportedAppUsage
+    /**
+     * Source codec type invalid. This is the default value used for codec
+     * type.
+     */
     public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
 
+    /**
+     * Represents the count of valid source codec types. Can be accessed via
+     * {@link #getMaxCodecType}.
+     */
+    private static final int SOURCE_CODEC_TYPE_MAX = 5;
+
     /** @hide */
     @IntDef(prefix = "CODEC_PRIORITY_", value = {
             CODEC_PRIORITY_DISABLED,
@@ -82,16 +96,24 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CodecPriority {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec priority disabled.
+     * Used to indicate that this codec is disabled and should not be used.
+     */
     public static final int CODEC_PRIORITY_DISABLED = -1;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec priority default.
+     * Default value used for codec priority.
+     */
     public static final int CODEC_PRIORITY_DEFAULT = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec priority highest.
+     * Used to indicate the highest priority a codec can have.
+     */
     public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
 
-
     /** @hide */
     @IntDef(prefix = "SAMPLE_RATE_", value = {
             SAMPLE_RATE_NONE,
@@ -105,28 +127,42 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface SampleRate {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 0 Hz. Default value used for
+     * codec sample rate.
+     */
     public static final int SAMPLE_RATE_NONE = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 44100 Hz.
+     */
     public static final int SAMPLE_RATE_44100 = 0x1 << 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 48000 Hz.
+     */
     public static final int SAMPLE_RATE_48000 = 0x1 << 1;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 88200 Hz.
+     */
     public static final int SAMPLE_RATE_88200 = 0x1 << 2;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 96000 Hz.
+     */
     public static final int SAMPLE_RATE_96000 = 0x1 << 3;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 176400 Hz.
+     */
     public static final int SAMPLE_RATE_176400 = 0x1 << 4;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec sample rate 192000 Hz.
+     */
     public static final int SAMPLE_RATE_192000 = 0x1 << 5;
 
-
     /** @hide */
     @IntDef(prefix = "BITS_PER_SAMPLE_", value = {
             BITS_PER_SAMPLE_NONE,
@@ -137,19 +173,27 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface BitsPerSample {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 0. Default value of the codec
+     * bits per sample.
+     */
     public static final int BITS_PER_SAMPLE_NONE = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 16.
+     */
     public static final int BITS_PER_SAMPLE_16 = 0x1 << 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 24.
+     */
     public static final int BITS_PER_SAMPLE_24 = 0x1 << 1;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec bits per sample 32.
+     */
     public static final int BITS_PER_SAMPLE_32 = 0x1 << 2;
 
-
     /** @hide */
     @IntDef(prefix = "CHANNEL_MODE_", value = {
             CHANNEL_MODE_NONE,
@@ -159,13 +203,20 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ChannelMode {}
 
-    @UnsupportedAppUsage
+    /**
+     * Codec channel mode NONE. Default value of the
+     * codec channel mode.
+     */
     public static final int CHANNEL_MODE_NONE = 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec channel mode MONO.
+     */
     public static final int CHANNEL_MODE_MONO = 0x1 << 0;
 
-    @UnsupportedAppUsage
+    /**
+     * Codec channel mode STEREO.
+     */
     public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
 
     private final @SourceCodecType int mCodecType;
@@ -178,6 +229,21 @@
     private final long mCodecSpecific3;
     private final long mCodecSpecific4;
 
+    /**
+     * Creates a new BluetoothCodecConfig.
+     *
+     * @param codecType the source codec type
+     * @param codecPriority the priority of this codec
+     * @param sampleRate the codec sample rate
+     * @param bitsPerSample the bits per sample of this codec
+     * @param channelMode the channel mode of this codec
+     * @param codecSpecific1 the specific value 1
+     * @param codecSpecific2 the specific value 2
+     * @param codecSpecific3 the specific value 3
+     * @param codecSpecific4 the specific value 4
+     * values to 0.
+     * @hide
+     */
     @UnsupportedAppUsage
     public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority,
             @SampleRate int sampleRate, @BitsPerSample int bitsPerSample,
@@ -195,17 +261,34 @@
         mCodecSpecific4 = codecSpecific4;
     }
 
-    @UnsupportedAppUsage
+    /**
+     * Creates a new BluetoothCodecConfig.
+     * <p> By default, the codec priority will be set
+     * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to
+     * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to
+     * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to
+     * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific
+     * values to 0.
+     *
+     * @param codecType the source codec type
+     */
     public BluetoothCodecConfig(@SourceCodecType int codecType) {
-        mCodecType = codecType;
-        mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
-        mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
-        mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
-        mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE;
-        mCodecSpecific1 = 0;
-        mCodecSpecific2 = 0;
-        mCodecSpecific3 = 0;
-        mCodecSpecific4 = 0;
+        this(codecType, BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                BluetoothCodecConfig.SAMPLE_RATE_NONE,
+                BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
+                BluetoothCodecConfig.CHANNEL_MODE_NONE, 0, 0, 0, 0);
+    }
+
+    private BluetoothCodecConfig(Parcel in) {
+        mCodecType = in.readInt();
+        mCodecPriority = in.readInt();
+        mSampleRate = in.readInt();
+        mBitsPerSample = in.readInt();
+        mChannelMode = in.readInt();
+        mCodecSpecific1 = in.readLong();
+        mCodecSpecific2 = in.readLong();
+        mCodecSpecific3 = in.readLong();
+        mCodecSpecific4 = in.readLong();
     }
 
     @Override
@@ -226,10 +309,8 @@
     }
 
     /**
-     * Returns a hash based on the config values
-     *
-     * @return a hash based on the config values
-     * @hide
+     * Returns a hash representation of this BluetoothCodecConfig
+     * based on all the config values.
      */
     @Override
     public int hashCode() {
@@ -239,32 +320,24 @@
     }
 
     /**
-     * Checks whether the object contains valid codec configuration.
-     *
-     * @return true if the object contains valid codec configuration, otherwise false.
-     * @hide
-     */
-    public boolean isValid() {
-        return (mSampleRate != SAMPLE_RATE_NONE)
-                && (mBitsPerSample != BITS_PER_SAMPLE_NONE)
-                && (mChannelMode != CHANNEL_MODE_NONE);
-    }
-
-    /**
      * Adds capability string to an existing string.
      *
-     * @param prevStr the previous string with the capabilities. Can be a null pointer.
-     * @param capStr the capability string to append to prevStr argument.
-     * @return the result string in the form "prevStr|capStr".
+     * @param prevStr the previous string with the capabilities. Can be a {@code null} pointer
+     * @param capStr the capability string to append to prevStr argument
+     * @return the result string in the form "prevStr|capStr"
      */
-    private static String appendCapabilityToString(String prevStr,
-            String capStr) {
+    private static String appendCapabilityToString(@Nullable String prevStr,
+            @NonNull String capStr) {
         if (prevStr == null) {
             return capStr;
         }
         return prevStr + "|" + capStr;
     }
 
+    /**
+     * Returns a {@link String} that describes each BluetoothCodecConfig parameter
+     * current value.
+     */
     @Override
     public String toString() {
         String sampleRateStr = null;
@@ -331,8 +404,6 @@
     }
 
     /**
-     * Always returns 0
-     *
      * @return 0
      * @hide
      */
@@ -344,20 +415,7 @@
     public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecConfig> CREATOR =
             new Parcelable.Creator<BluetoothCodecConfig>() {
                 public BluetoothCodecConfig createFromParcel(Parcel in) {
-                    final int codecType = in.readInt();
-                    final int codecPriority = in.readInt();
-                    final int sampleRate = in.readInt();
-                    final int bitsPerSample = in.readInt();
-                    final int channelMode = in.readInt();
-                    final long codecSpecific1 = in.readLong();
-                    final long codecSpecific2 = in.readLong();
-                    final long codecSpecific3 = in.readLong();
-                    final long codecSpecific4 = in.readLong();
-                    return new BluetoothCodecConfig(codecType, codecPriority,
-                            sampleRate, bitsPerSample,
-                            channelMode, codecSpecific1,
-                            codecSpecific2, codecSpecific3,
-                            codecSpecific4);
+                    return new BluetoothCodecConfig(in);
                 }
 
                 public BluetoothCodecConfig[] newArray(int size) {
@@ -368,8 +426,8 @@
     /**
      * Flattens the object to a parcel
      *
-     * @param out The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written.
+     * @param out The Parcel in which the object should be written
+     * @param flags Additional flags about how the object should be written
      *
      * @hide
      */
@@ -387,9 +445,8 @@
     }
 
     /**
-     * Gets the codec name.
-     *
-     * @return the codec name
+     * Returns the codec name converted to {@link String}.
+     * @hide
      */
     public @NonNull String getCodecName() {
         switch (mCodecType) {
@@ -412,137 +469,100 @@
     }
 
     /**
-     * Gets the codec type.
-     * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}.
-     *
-     * @return the codec type
+     * Returns the source codec type of this config.
      */
-    @UnsupportedAppUsage
     public @SourceCodecType int getCodecType() {
         return mCodecType;
     }
 
     /**
+     * Returns the valid codec types count.
+     */
+    public static int getMaxCodecType() {
+        return SOURCE_CODEC_TYPE_MAX;
+    }
+
+    /**
      * Checks whether the codec is mandatory.
+     * <p> The actual mandatory codec type for Android Bluetooth audio is SBC.
+     * See {@link #SOURCE_CODEC_TYPE_SBC}.
      *
-     * @return true if the codec is mandatory, otherwise false.
+     * @return {@code true} if the codec is mandatory, {@code false} otherwise
+     * @hide
      */
     public boolean isMandatoryCodec() {
         return mCodecType == SOURCE_CODEC_TYPE_SBC;
     }
 
     /**
-     * Gets the codec selection priority.
-     * The codec selection priority is relative to other codecs: larger value
-     * means higher priority. If 0, reset to default.
-     *
-     * @return the codec priority
+     * Returns the codec selection priority.
+     * <p>The codec selection priority is relative to other codecs: larger value
+     * means higher priority.
      */
-    @UnsupportedAppUsage
     public @CodecPriority int getCodecPriority() {
         return mCodecPriority;
     }
 
     /**
      * Sets the codec selection priority.
-     * The codec selection priority is relative to other codecs: larger value
-     * means higher priority. If 0, reset to default.
+     * <p>The codec selection priority is relative to other codecs: larger value
+     * means higher priority.
      *
-     * @param codecPriority the codec priority
+     * @param codecPriority the priority this codec should have
      * @hide
      */
-    @UnsupportedAppUsage
     public void setCodecPriority(@CodecPriority int codecPriority) {
         mCodecPriority = codecPriority;
     }
 
     /**
-     * Gets the codec sample rate. The value can be a bitmask with all
-     * supported sample rates:
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_48000} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_88200} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_96000} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_176400} or
-     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_192000}
-     *
-     * @return the codec sample rate
+     * Returns the codec sample rate. The value can be a bitmask with all
+     * supported sample rates.
      */
-    @UnsupportedAppUsage
     public @SampleRate int getSampleRate() {
         return mSampleRate;
     }
 
     /**
-     * Gets the codec bits per sample. The value can be a bitmask with all
-     * bits per sample supported:
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_24} or
-     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_32}
-     *
-     * @return the codec bits per sample
+     * Returns the codec bits per sample. The value can be a bitmask with all
+     * bits per sample supported.
      */
-    @UnsupportedAppUsage
     public @BitsPerSample int getBitsPerSample() {
         return mBitsPerSample;
     }
 
     /**
-     * Gets the codec channel mode. The value can be a bitmask with all
-     * supported channel modes:
-     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or
-     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or
-     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_STEREO}
-     *
-     * @return the codec channel mode
-     * @hide
+     * Returns the codec channel mode. The value can be a bitmask with all
+     * supported channel modes.
      */
-    @UnsupportedAppUsage
     public @ChannelMode int getChannelMode() {
         return mChannelMode;
     }
 
     /**
-     * Gets a codec specific value1.
-     *
-     * @return a codec specific value1.
+     * Returns the codec specific value1.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific1() {
         return mCodecSpecific1;
     }
 
     /**
-     * Gets a codec specific value2.
-     *
-     * @return a codec specific value2
-     * @hide
+     * Returns the codec specific value2.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific2() {
         return mCodecSpecific2;
     }
 
     /**
-     * Gets a codec specific value3.
-     *
-     * @return a codec specific value3
-     * @hide
+     * Returns the codec specific value3.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific3() {
         return mCodecSpecific3;
     }
 
     /**
-     * Gets a codec specific value4.
-     *
-     * @return a codec specific value4
-     * @hide
+     * Returns the codec specific value4.
      */
-    @UnsupportedAppUsage
     public long getCodecSpecific4() {
         return mCodecSpecific4;
     }
@@ -551,7 +571,7 @@
      * Checks whether a value set presented by a bitmask has zero or single bit
      *
      * @param valueSet the value set presented by a bitmask
-     * @return true if the valueSet contains zero or single bit, otherwise false.
+     * @return {@code true} if the valueSet contains zero or single bit, {@code false} otherwise
      * @hide
      */
     private static boolean hasSingleBit(int valueSet) {
@@ -559,9 +579,7 @@
     }
 
     /**
-     * Checks whether the object contains none or single sample rate.
-     *
-     * @return true if the object contains none or single sample rate, otherwise false.
+     * Returns whether the object contains none or single sample rate.
      * @hide
      */
     public boolean hasSingleSampleRate() {
@@ -569,9 +587,7 @@
     }
 
     /**
-     * Checks whether the object contains none or single bits per sample.
-     *
-     * @return true if the object contains none or single bits per sample, otherwise false.
+     * Returns whether the object contains none or single bits per sample.
      * @hide
      */
     public boolean hasSingleBitsPerSample() {
@@ -579,9 +595,7 @@
     }
 
     /**
-     * Checks whether the object contains none or single channel mode.
-     *
-     * @return true if the object contains none or single channel mode, otherwise false.
+     * Returns whether the object contains none or single channel mode.
      * @hide
      */
     public boolean hasSingleChannelMode() {
@@ -589,10 +603,10 @@
     }
 
     /**
-     * Checks whether the audio feeding parameters are same.
+     * Checks whether the audio feeding parameters are the same.
      *
      * @param other the codec config to compare against
-     * @return true if the audio feeding parameters are same, otherwise false
+     * @return {@code true} if the audio feeding parameters are same, {@code false} otherwise
      * @hide
      */
     public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) {
@@ -606,7 +620,7 @@
      * Any parameters with NONE value will be considered to be a wildcard matching.
      *
      * @param other the codec config to compare against
-     * @return true if the audio feeding parameters are similar, otherwise false.
+     * @return {@code true} if the audio feeding parameters are similar, {@code false} otherwise
      * @hide
      */
     public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) {
@@ -614,18 +628,18 @@
             return false;
         }
         int sampleRate = other.mSampleRate;
-        if (mSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE
-                || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+        if (mSampleRate == SAMPLE_RATE_NONE
+                || sampleRate == SAMPLE_RATE_NONE) {
             sampleRate = mSampleRate;
         }
         int bitsPerSample = other.mBitsPerSample;
-        if (mBitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE
-                || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+        if (mBitsPerSample == BITS_PER_SAMPLE_NONE
+                || bitsPerSample == BITS_PER_SAMPLE_NONE) {
             bitsPerSample = mBitsPerSample;
         }
         int channelMode = other.mChannelMode;
-        if (mChannelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE
-                || channelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+        if (mChannelMode == CHANNEL_MODE_NONE
+                || channelMode == CHANNEL_MODE_NONE) {
             channelMode = mChannelMode;
         }
         return sameAudioFeedingParameters(new BluetoothCodecConfig(
@@ -636,25 +650,158 @@
 
     /**
      * Checks whether the codec specific parameters are the same.
+     * <p> Currently, only AAC VBR and LDAC Playback Quality on CodecSpecific1
+     * are compared.
      *
      * @param other the codec config to compare against
-     * @return true if the codec specific parameters are the same, otherwise false.
+     * @return {@code true} if the codec specific parameters are the same, {@code false} otherwise
      * @hide
      */
     public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) {
         if (other == null && mCodecType != other.mCodecType) {
             return false;
         }
-        // Currently we only care about the AAC VBR and LDAC Playback Quality at CodecSpecific1
         switch (mCodecType) {
             case SOURCE_CODEC_TYPE_AAC:
             case SOURCE_CODEC_TYPE_LDAC:
                 if (mCodecSpecific1 != other.mCodecSpecific1) {
                     return false;
                 }
-                // fall through
             default:
                 return true;
         }
     }
+
+    /**
+     * Builder for {@link BluetoothCodecConfig}.
+     * <p> By default, the codec type will be set to
+     * {@link BluetoothCodecConfig#SOURCE_CODEC_TYPE_INVALID}, the codec priority
+     * to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to
+     * {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to
+     * {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to
+     * {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific
+     * values to 0.
+     */
+    public static final class Builder {
+        private int mCodecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
+        private int mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        private int mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
+        private int mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
+        private int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE;
+        private long mCodecSpecific1 = 0;
+        private long mCodecSpecific2 = 0;
+        private long mCodecSpecific3 = 0;
+        private long mCodecSpecific4 = 0;
+
+        /**
+         * Set codec type for Bluetooth codec config.
+         *
+         * @param codecType of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecType(@SourceCodecType int codecType) {
+            mCodecType = codecType;
+            return this;
+        }
+
+        /**
+         * Set codec priority for Bluetooth codec config.
+         *
+         * @param codecPriority of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecPriority(@CodecPriority int codecPriority) {
+            mCodecPriority = codecPriority;
+            return this;
+        }
+
+        /**
+         * Set sample rate for Bluetooth codec config.
+         *
+         * @param sampleRate of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setSampleRate(@SampleRate int sampleRate) {
+            mSampleRate = sampleRate;
+            return this;
+        }
+
+        /**
+         * Set the bits per sample for Bluetooth codec config.
+         *
+         * @param bitsPerSample of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setBitsPerSample(@BitsPerSample int bitsPerSample) {
+            mBitsPerSample = bitsPerSample;
+            return this;
+        }
+
+        /**
+         * Set the channel mode for Bluetooth codec config.
+         *
+         * @param channelMode of this codec
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setChannelMode(@ChannelMode int channelMode) {
+            mChannelMode = channelMode;
+            return this;
+        }
+
+        /**
+         * Set the first codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific1 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific1(long codecSpecific1) {
+            mCodecSpecific1 = codecSpecific1;
+            return this;
+        }
+
+        /**
+         * Set the second codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific2 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific2(long codecSpecific2) {
+            mCodecSpecific2 = codecSpecific2;
+            return this;
+        }
+
+        /**
+         * Set the third codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific3 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific3(long codecSpecific3) {
+            mCodecSpecific3 = codecSpecific3;
+            return this;
+        }
+
+        /**
+         * Set the fourth codec specific values for Bluetooth codec config.
+         *
+         * @param codecSpecific4 codec specific value or 0 if default
+         * @return the same Builder instance
+         */
+        public @NonNull Builder setCodecSpecific4(long codecSpecific4) {
+            mCodecSpecific4 = codecSpecific4;
+            return this;
+        }
+
+        /**
+         * Build {@link BluetoothCodecConfig}.
+         * @return new BluetoothCodecConfig built
+         */
+        public @NonNull BluetoothCodecConfig build() {
+            return new BluetoothCodecConfig(mCodecType, mCodecPriority,
+                    mSampleRate, mBitsPerSample,
+                    mChannelMode, mCodecSpecific1,
+                    mCodecSpecific2, mCodecSpecific3,
+                    mCodecSpecific4);
+        }
+    }
 }
diff --git a/core/java/android/bluetooth/BluetoothCodecStatus.java b/core/java/android/bluetooth/BluetoothCodecStatus.java
index 7764ebe..02606fe 100644
--- a/core/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/core/java/android/bluetooth/BluetoothCodecStatus.java
@@ -16,12 +16,13 @@
 
 package android.bluetooth;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -29,8 +30,6 @@
  * A2DP source device.
  *
  * {@see BluetoothA2dp}
- *
- * {@hide}
  */
 public final class BluetoothCodecStatus implements Parcelable {
     /**
@@ -39,22 +38,27 @@
      * This extra represents the current codec status of the A2DP
      * profile.
      */
-    @UnsupportedAppUsage
     public static final String EXTRA_CODEC_STATUS =
             "android.bluetooth.extra.CODEC_STATUS";
 
     private final @Nullable BluetoothCodecConfig mCodecConfig;
-    private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
-    private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
+    private final @Nullable List<BluetoothCodecConfig> mCodecsLocalCapabilities;
+    private final @Nullable List<BluetoothCodecConfig> mCodecsSelectableCapabilities;
 
     public BluetoothCodecStatus(@Nullable BluetoothCodecConfig codecConfig,
-            @Nullable BluetoothCodecConfig[] codecsLocalCapabilities,
-            @Nullable BluetoothCodecConfig[] codecsSelectableCapabilities) {
+            @Nullable List<BluetoothCodecConfig> codecsLocalCapabilities,
+            @Nullable List<BluetoothCodecConfig> codecsSelectableCapabilities) {
         mCodecConfig = codecConfig;
         mCodecsLocalCapabilities = codecsLocalCapabilities;
         mCodecsSelectableCapabilities = codecsSelectableCapabilities;
     }
 
+    private BluetoothCodecStatus(Parcel in) {
+        mCodecConfig = in.readTypedObject(BluetoothCodecConfig.CREATOR);
+        mCodecsLocalCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR);
+        mCodecsSelectableCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o instanceof BluetoothCodecStatus) {
@@ -68,26 +72,25 @@
     }
 
     /**
-     * Checks whether two arrays of capabilities contain same capabilities.
-     * The order of the capabilities in each array is ignored.
+     * Checks whether two lists of capabilities contain same capabilities.
+     * The order of the capabilities in each list is ignored.
      *
-     * @param c1 the first array of capabilities to compare
-     * @param c2 the second array of capabilities to compare
-     * @return true if both arrays contain same capabilities
-     * @hide
+     * @param c1 the first list of capabilities to compare
+     * @param c2 the second list of capabilities to compare
+     * @return {@code true} if both lists contain same capabilities
      */
-    public static boolean sameCapabilities(BluetoothCodecConfig[] c1,
-                                           BluetoothCodecConfig[] c2) {
+    private static boolean sameCapabilities(@Nullable List<BluetoothCodecConfig> c1,
+                                           @Nullable List<BluetoothCodecConfig> c2) {
         if (c1 == null) {
             return (c2 == null);
         }
         if (c2 == null) {
             return false;
         }
-        if (c1.length != c2.length) {
+        if (c1.size() != c2.size()) {
             return false;
         }
-        return Arrays.asList(c1).containsAll(Arrays.asList(c2));
+        return c1.containsAll(c2);
     }
 
     /**
@@ -95,10 +98,9 @@
      * Any parameters of the codec config with NONE value will be considered a wildcard matching.
      *
      * @param codecConfig the codec config to compare against
-     * @return true if the codec config matches, otherwise false
-     * @hide
+     * @return {@code true} if the codec config matches, {@code false} otherwise
      */
-    public boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig) {
+    public boolean isCodecConfigSelectable(@Nullable BluetoothCodecConfig codecConfig) {
         if (codecConfig == null || !codecConfig.hasSingleSampleRate()
                 || !codecConfig.hasSingleBitsPerSample() || !codecConfig.hasSingleChannelMode()) {
             return false;
@@ -128,10 +130,7 @@
     }
 
     /**
-     * Returns a hash based on the codec config and local capabilities
-     *
-     * @return a hash based on the config values
-     * @hide
+     * Returns a hash based on the codec config and local capabilities.
      */
     @Override
     public int hashCode() {
@@ -139,17 +138,19 @@
                 mCodecsLocalCapabilities);
     }
 
+    /**
+     * Returns a {@link String} that describes each BluetoothCodecStatus parameter
+     * current value.
+     */
     @Override
     public String toString() {
         return "{mCodecConfig:" + mCodecConfig
-                + ",mCodecsLocalCapabilities:" + Arrays.toString(mCodecsLocalCapabilities)
-                + ",mCodecsSelectableCapabilities:" + Arrays.toString(mCodecsSelectableCapabilities)
+                + ",mCodecsLocalCapabilities:" + mCodecsLocalCapabilities
+                + ",mCodecsSelectableCapabilities:" + mCodecsSelectableCapabilities
                 + "}";
     }
 
     /**
-     * Always returns 0
-     *
      * @return 0
      * @hide
      */
@@ -161,16 +162,7 @@
     public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecStatus> CREATOR =
             new Parcelable.Creator<BluetoothCodecStatus>() {
                 public BluetoothCodecStatus createFromParcel(Parcel in) {
-                    final BluetoothCodecConfig codecConfig = in.readTypedObject(
-                            BluetoothCodecConfig.CREATOR);
-                    final BluetoothCodecConfig[] codecsLocalCapabilities = in.createTypedArray(
-                            BluetoothCodecConfig.CREATOR);
-                    final BluetoothCodecConfig[] codecsSelectableCapabilities = in.createTypedArray(
-                            BluetoothCodecConfig.CREATOR);
-
-                    return new BluetoothCodecStatus(codecConfig,
-                            codecsLocalCapabilities,
-                            codecsSelectableCapabilities);
+                    return new BluetoothCodecStatus(in);
                 }
 
                 public BluetoothCodecStatus[] newArray(int size) {
@@ -179,47 +171,38 @@
             };
 
     /**
-     * Flattens the object to a parcel
+     * Flattens the object to a parcel.
      *
-     * @param out The Parcel in which the object should be written.
-     * @param flags Additional flags about how the object should be written.
-     *
-     * @hide
+     * @param out The Parcel in which the object should be written
+     * @param flags Additional flags about how the object should be written
      */
     @Override
-    public void writeToParcel(Parcel out, int flags) {
+    public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeTypedObject(mCodecConfig, 0);
-        out.writeTypedArray(mCodecsLocalCapabilities, 0);
-        out.writeTypedArray(mCodecsSelectableCapabilities, 0);
+        out.writeTypedList(mCodecsLocalCapabilities);
+        out.writeTypedList(mCodecsSelectableCapabilities);
     }
 
     /**
-     * Gets the current codec configuration.
-     *
-     * @return the current codec configuration
+     * Returns the current codec configuration.
      */
-    @UnsupportedAppUsage
     public @Nullable BluetoothCodecConfig getCodecConfig() {
         return mCodecConfig;
     }
 
     /**
-     * Gets the codecs local capabilities.
-     *
-     * @return an array with the codecs local capabilities
+     * Returns the codecs local capabilities.
      */
-    @UnsupportedAppUsage
-    public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() {
-        return mCodecsLocalCapabilities;
+    public @NonNull List<BluetoothCodecConfig> getCodecsLocalCapabilities() {
+        return (mCodecsLocalCapabilities == null)
+                ? Collections.emptyList() : mCodecsLocalCapabilities;
     }
 
     /**
-     * Gets the codecs selectable capabilities.
-     *
-     * @return an array with the codecs selectable capabilities
+     * Returns the codecs selectable capabilities.
      */
-    @UnsupportedAppUsage
-    public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
-        return mCodecsSelectableCapabilities;
+    public @NonNull List<BluetoothCodecConfig> getCodecsSelectableCapabilities() {
+        return (mCodecsSelectableCapabilities == null)
+                ? Collections.emptyList() : mCodecsSelectableCapabilities;
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
index f0a8df0..ba57ec4 100644
--- a/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
+++ b/core/java/android/bluetooth/BluetoothCsipSetCoordinator.java
@@ -17,6 +17,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
@@ -27,13 +29,14 @@
 import android.annotation.SystemApi;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -41,6 +44,7 @@
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth CSIP set coordinator.
@@ -229,8 +233,7 @@
                     IBluetoothCsipSetCoordinator.class.getName()) {
                 @Override
                 public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) {
-                    return IBluetoothCsipSetCoordinator.Stub.asInterface(
-                            Binder.allowBlocking(service));
+                    return IBluetoothCsipSetCoordinator.Stub.asInterface(service);
                 }
             };
 
@@ -283,26 +286,27 @@
     public
     @Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor,
             @Nullable ClientLockCallback cb) {
-        if (VDBG) {
-            log("groupLockSet()");
-        }
+        if (VDBG) log("groupLockSet()");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                IBluetoothCsipSetCoordinatorLockCallback delegate = null;
-                if ((executor != null) && (cb != null)) {
-                    delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
-                }
-                return service.groupLock(groupId, delegate, mAttributionSource).getUuid();
+        final UUID defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            IBluetoothCsipSetCoordinatorLockCallback delegate = null;
+            if ((executor != null) && (cb != null)) {
+                delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
+            try {
+                final SynchronousResultReceiver<ParcelUuid> recv = new SynchronousResultReceiver();
+                service.groupLock(groupId, delegate, mAttributionSource, recv);
+                final ParcelUuid ret = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+                return ret == null ? defaultValue : ret.getUuid();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            return null;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return null;
         }
+        return defaultValue;
     }
 
     /**
@@ -315,27 +319,26 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean groupUnlock(@NonNull UUID lockUuid) {
-        if (VDBG) {
-            log("groupLockSet()");
-        }
+        if (VDBG) log("groupLockSet()");
         if (lockUuid == null) {
             return false;
         }
-
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                 return true;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -348,22 +351,22 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) {
-        if (VDBG) {
-            log("getGroupUuidMapByDevice()");
-        }
+        if (VDBG) log("getGroupUuidMapByDevice()");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                return service.getGroupUuidMapByDevice(device, mAttributionSource);
+        final Map defaultValue = new HashMap<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Map> recv = new SynchronousResultReceiver();
+                service.getGroupUuidMapByDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return new HashMap<>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new HashMap<>();
         }
+        return defaultValue;
     }
 
     /**
@@ -376,22 +379,23 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) {
-        if (VDBG) {
-            log("getAllGroupIds()");
-        }
+        if (VDBG) log("getAllGroupIds()");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                return service.getAllGroupIds(uuid, mAttributionSource);
+        final List<Integer> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<Integer>> recv =
+                        new SynchronousResultReceiver();
+                service.getAllGroupIds(uuid, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return new ArrayList<Integer>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<Integer>();
         }
+        return defaultValue;
     }
 
     /**
@@ -399,22 +403,23 @@
      */
     @Override
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
-        if (VDBG) {
-            log("getConnectedDevices()");
-        }
+        if (VDBG) log("getConnectedDevices()");
         final IBluetoothCsipSetCoordinator service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getConnectedDevices(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -422,24 +427,24 @@
      */
     @Override
     public
-    @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
-            @NonNull int[] states) {
-        if (VDBG) {
-            log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
-        }
+    @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
+        if (VDBG) log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getDevicesMatchingConnectionStates(states, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -447,24 +452,23 @@
      */
     @Override
     public
-    @BluetoothProfile.BtProfileState int getConnectionState(
-            @Nullable BluetoothDevice device) {
-        if (VDBG) {
-            log("getState(" + device + ")");
-        }
+    @BluetoothProfile.BtProfileState int getConnectionState(@Nullable BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -484,26 +488,24 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean setConnectionPolicy(
             @Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
-        if (DBG) {
-            log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        }
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -521,22 +523,22 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
     public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
-        if (VDBG) {
-            log("getConnectionPolicy(" + device + ")");
-        }
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothCsipSetCoordinator service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-            }
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 6e918bd..93f0268 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -32,7 +32,6 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.companion.AssociationRequest;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.os.Build;
@@ -1178,7 +1177,7 @@
 
         mAddress = address;
         mAddressType = ADDRESS_TYPE_PUBLIC;
-        mAttributionSource = BluetoothManager.resolveAttributionSource(null);
+        mAttributionSource = AttributionSource.myAttributionSource();
     }
 
     /** {@hide} */
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 4e7c01a..fe8d1ba 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -1806,32 +1806,33 @@
     }
 
     /**
-     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+     * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
      * with {@link BluetoothProfile#GATT} as argument
-     *
      * @throws UnsupportedOperationException
      */
     @Override
     @RequiresNoPermission
+    @Deprecated
     public int getConnectionState(BluetoothDevice device) {
         throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
     }
 
     /**
-     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+     * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
      * with {@link BluetoothProfile#GATT} as argument
      *
      * @throws UnsupportedOperationException
      */
     @Override
     @RequiresNoPermission
+    @Deprecated
     public List<BluetoothDevice> getConnectedDevices() {
         throw new UnsupportedOperationException(
                 "Use BluetoothManager#getConnectedDevices instead.");
     }
 
     /**
-     * Not supported - please use
+     * @deprecated Not supported - please use
      * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
      * with {@link BluetoothProfile#GATT} as first argument
      *
@@ -1839,6 +1840,7 @@
      */
     @Override
     @RequiresNoPermission
+    @Deprecated
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         throw new UnsupportedOperationException(
                 "Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index c5e986e..053e0db 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -313,7 +313,7 @@
     };
 
     private BluetoothGattCharacteristic(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
         mInstance = in.readInt();
         mProperties = in.readInt();
         mPermissions = in.readInt();
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index a35d5b9..6ed4706 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -187,7 +187,7 @@
     };
 
     private BluetoothGattDescriptor(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
         mInstance = in.readInt();
         mPermissions = in.readInt();
     }
diff --git a/core/java/android/bluetooth/BluetoothGattIncludedService.java b/core/java/android/bluetooth/BluetoothGattIncludedService.java
index 5580619..1ae2ca0 100644
--- a/core/java/android/bluetooth/BluetoothGattIncludedService.java
+++ b/core/java/android/bluetooth/BluetoothGattIncludedService.java
@@ -76,7 +76,7 @@
     };
 
     private BluetoothGattIncludedService(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
         mInstanceId = in.readInt();
         mServiceType = in.readInt();
     }
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index f64d09f..36bc477 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -180,7 +180,7 @@
     };
 
     private BluetoothGattService(Parcel in) {
-        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mUuid = ((ParcelUuid) in.readParcelable(null, android.os.ParcelUuid.class)).getUuid();
         mInstanceId = in.readInt();
         mServiceType = in.readInt();
 
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index c046324..f2a6276 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -27,12 +29,10 @@
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -42,8 +42,11 @@
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Public API for controlling the Bluetooth Headset Service. This includes both
@@ -480,16 +483,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -521,16 +528,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -542,18 +553,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevicesWithAttribution(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevicesWithAttribution(mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -565,18 +581,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -588,59 +609,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionStateWithAttribution(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
-    }
-
-    /**
-     * Set priority of the profile
-     *
-     * <p> The device should already be paired.
-     * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
-     * {@link BluetoothProfile#PRIORITY_OFF}
-     *
-     * @param device Paired bluetooth device
-     * @param priority
-     * @return true if priority is set, false on error
-     * @hide
-     * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
-     * @removed
-     */
-    @Deprecated
-    @SystemApi
-    @RequiresLegacyBluetoothAdminPermission
-    @RequiresBluetoothConnectPermission
-    @RequiresPermission(allOf = {
-            android.Manifest.permission.BLUETOOTH_CONNECT,
-            android.Manifest.permission.MODIFY_PHONE_STATE,
-    })
-    public boolean setPriority(BluetoothDevice device, int priority) {
-        if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (priority != BluetoothProfile.PRIORITY_OFF
-                    && priority != BluetoothProfile.PRIORITY_ON) {
-                return false;
-            }
-            try {
-                return service.setPriority(
-                        device, BluetoothAdapter.priorityToConnectionPolicy(priority),
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -666,20 +648,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -699,18 +683,7 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return BluetoothAdapter.connectionPolicyToPriority(
-                        service.getPriority(device, mAttributionSource));
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.PRIORITY_OFF;
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.PRIORITY_OFF;
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
     }
 
     /**
@@ -733,16 +706,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -757,15 +734,20 @@
     public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
         if (DBG) log("isNoiseReductionSupported()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isNoiseReductionSupported(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isNoiseReductionSupported(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -780,15 +762,20 @@
     public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
         if (DBG) log("isVoiceRecognitionSupported()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isVoiceRecognitionSupported(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isVoiceRecognitionSupported(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -819,15 +806,20 @@
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.startVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.startVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -848,15 +840,20 @@
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.stopVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.stopVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -871,15 +868,20 @@
     public boolean isAudioConnected(BluetoothDevice device) {
         if (VDBG) log("isAudioConnected()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.isAudioConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isAudioConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -905,17 +907,20 @@
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
         final IBluetoothHeadset service = mService;
-        if (service != null && !isDisabled()) {
-            try {
-                return service.getAudioState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (!isDisabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getAudioState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -933,15 +938,17 @@
     public void setAudioRouteAllowed(boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.setAudioRouteAllowed(allowed, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setAudioRouteAllowed(allowed, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -956,17 +963,20 @@
     public boolean getAudioRouteAllowed() {
         if (VDBG) log("getAudioRouteAllowed");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.getAudioRouteAllowed(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getAudioRouteAllowed(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -981,15 +991,17 @@
     public void setForceScoAudio(boolean forced) {
         if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.setForceScoAudio(forced, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setForceScoAudio(forced, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -1006,16 +1018,20 @@
     public boolean isAudioOn() {
         if (VDBG) log("isAudioOn()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.isAudioOn(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isAudioOn(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-
+        return defaultValue;
     }
 
     /**
@@ -1040,18 +1056,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connectAudio() {
+        if (VDBG) log("connectAudio()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.connectAudio(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectAudio(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1069,18 +1089,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnectAudio() {
+        if (VDBG) log("disconnectAudio()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.disconnectAudio(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectAudio(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1114,17 +1138,20 @@
     public boolean startScoUsingVirtualVoiceCall() {
         if (DBG) log("startScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.startScoUsingVirtualVoiceCall(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.startScoUsingVirtualVoiceCall(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1149,17 +1176,20 @@
     public boolean stopScoUsingVirtualVoiceCall() {
         if (DBG) log("stopScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.stopScoUsingVirtualVoiceCall(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.stopScoUsingVirtualVoiceCall(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1179,16 +1209,16 @@
     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
             int type, String name) {
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
                 service.phoneStateChanged(numActive, numHeld, callState, number, type, name,
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
+            } catch (RemoteException  e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
         }
     }
 
@@ -1205,16 +1235,18 @@
     public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
             String number, int type) {
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.clccResponse(index, direction, status, mode, mpty, number, type,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.clccResponse(index, direction, status, mode, mpty, number, type,
+                        mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -1246,18 +1278,21 @@
             throw new IllegalArgumentException("command is null");
         }
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.sendVendorSpecificResultCode(device, command, arg,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendVendorSpecificResultCode(device, command, arg,
+                        mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1291,17 +1326,20 @@
             Log.d(TAG, "setActiveDevice: " + device);
         }
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
-            try {
-                return service.setActiveDevice(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && (device == null || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1317,22 +1355,25 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothDevice getActiveDevice() {
-        if (VDBG) {
-            Log.d(TAG, "getActiveDevice");
-        }
+        if (VDBG) Log.d(TAG, "getActiveDevice");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getActiveDevice(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final BluetoothDevice defaultValue = null;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevice(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -1347,21 +1388,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
     public boolean isInbandRingingEnabled() {
-        if (DBG) {
-            log("isInbandRingingEnabled()");
-        }
+        if (DBG) log("isInbandRingingEnabled()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.isInbandRingingEnabled(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isInbandRingingEnabled(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1381,7 +1423,7 @@
         @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
+            mService = IBluetoothHeadset.Stub.asInterface(service);
             mHandler.sendMessage(mHandler.obtainMessage(
                     MESSAGE_HEADSET_SERVICE_CONNECTED));
         }
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index a5a2470..7d7a7f7 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -23,18 +25,19 @@
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Public API to control Hands Free Profile (HFP role only).
@@ -433,7 +436,7 @@
                     "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
                 @Override
                 public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
-                    return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHeadsetClient.Stub.asInterface(service);
                 }
     };
 
@@ -480,18 +483,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -508,18 +514,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -532,19 +541,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -559,20 +573,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -586,18 +604,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -635,22 +656,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -687,18 +709,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final @ConnectionPolicy int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -716,17 +741,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.startVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.startVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -740,20 +769,23 @@
      */
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
-    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
-                                             String atCommand) {
+    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) {
         if (DBG) log("sendVendorSpecificCommand()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -771,17 +803,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.stopVoiceRecognition(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.stopVoiceRecognition(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -794,18 +830,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final List<BluetoothHeadsetClientCall> defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
+                final SynchronousResultReceiver<List<BluetoothHeadsetClientCall>> recv =
+                        new SynchronousResultReceiver();
+                service.getCurrentCalls(device, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getCurrentCalls(device, mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -818,17 +860,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
         if (DBG) log("getCurrentAgEvents()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final Bundle defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getCurrentAgEvents(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver();
+                service.getCurrentAgEvents(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -845,17 +891,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean acceptCall(BluetoothDevice device, int flag) {
         if (DBG) log("acceptCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.acceptCall(device, flag, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.acceptCall(device, flag, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -869,17 +919,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean holdCall(BluetoothDevice device) {
         if (DBG) log("holdCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.holdCall(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.holdCall(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -898,17 +952,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean rejectCall(BluetoothDevice device) {
         if (DBG) log("rejectCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.rejectCall(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.rejectCall(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -931,17 +989,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
         if (DBG) log("terminateCall()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.terminateCall(device, call, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.terminateCall(device, call, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -962,17 +1024,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean enterPrivateMode(BluetoothDevice device, int index) {
         if (DBG) log("enterPrivateMode()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.enterPrivateMode(device, index, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.enterPrivateMode(device, index, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -992,17 +1058,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean explicitCallTransfer(BluetoothDevice device) {
         if (DBG) log("explicitCallTransfer()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.explicitCallTransfer(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.explicitCallTransfer(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1018,18 +1088,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
         if (DBG) log("dial()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final BluetoothHeadsetClientCall defaultValue = null;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
+                final SynchronousResultReceiver<BluetoothHeadsetClientCall> recv =
+                        new SynchronousResultReceiver();
+                service.dial(device, number, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.dial(device, number, mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -1046,17 +1122,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendDTMF(BluetoothDevice device, byte code) {
         if (DBG) log("sendDTMF()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendDTMF(device, code, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendDTMF(device, code, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1075,17 +1155,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
         if (DBG) log("getLastVoiceTagNumber()");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getLastVoiceTagNumber(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getLastVoiceTagNumber(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1098,17 +1182,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
+        final IBluetoothHeadsetClient service = getService();
+        final int defaultValue = BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.getAudioState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getAudioState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            return defaultValue;
         }
         return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
     }
@@ -1124,17 +1212,18 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                service.setAudioRouteAllowed(device, allowed, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final IBluetoothHeadsetClient service = getService();
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setAudioRouteAllowed(device, allowed, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
     }
 
@@ -1149,19 +1238,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean getAudioRouteAllowed(BluetoothDevice device) {
         if (VDBG) log("getAudioRouteAllowed");
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getAudioRouteAllowed(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getAudioRouteAllowed(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1176,19 +1267,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connectAudio(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.connectAudio(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (VDBG) log("connectAudio");
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connectAudio(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1203,19 +1297,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnectAudio(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.disconnectAudio(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (VDBG) log("disconnectAudio");
+        final IBluetoothHeadsetClient service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnectAudio(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -1227,19 +1324,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service =
-                getService();
-        if (service != null && isEnabled()) {
-            try {
-                return service.getCurrentAgFeatures(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        if (VDBG) log("getCurrentAgFeatures");
+        final IBluetoothHeadsetClient service = getService();
+        final Bundle defaultValue = null;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Bundle> recv = new SynchronousResultReceiver();
+                service.getCurrentAgFeatures(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index 3f1ef84..e9dd761 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.os.Build;
 import android.os.Parcel;
@@ -293,7 +292,7 @@
             new Parcelable.Creator<BluetoothHeadsetClientCall>() {
                 @Override
                 public BluetoothHeadsetClientCall createFromParcel(Parcel in) {
-                    return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null),
+                    return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null, android.bluetooth.BluetoothDevice.class),
                             in.readInt(), UUID.fromString(in.readString()), in.readInt(),
                             in.readString(), in.readInt() == 1, in.readInt() == 1,
                             in.readInt() == 1);
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 183f4d5..339a75f 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -16,29 +16,30 @@
 
 package android.bluetooth;
 
-import android.Manifest;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Hearing Aid profile.
@@ -139,7 +140,7 @@
                     "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) {
                 @Override
                 public IBluetoothHearingAid getServiceInterface(IBinder service) {
-                    return IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHearingAid.Stub.asInterface(service);
                 }
     };
 
@@ -184,16 +185,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.connect(device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -226,16 +231,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -247,17 +256,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -270,18 +285,23 @@
     @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -294,17 +314,20 @@
     @NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionState(device, mAttributionSource);
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.STATE_DISCONNECTED;
         }
+        return defaultValue;
     }
 
     /**
@@ -333,18 +356,20 @@
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && ((device == null) || isValidDevice(device))) {
-                service.setActiveDevice(device, mAttributionSource);
-                return true;
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && ((device == null) || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -362,17 +387,23 @@
     public @NonNull List<BluetoothDevice> getActiveDevices() {
         if (VDBG) log("getActiveDevices()");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getActiveDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<>();
         }
+        return defaultValue;
     }
 
     /**
@@ -419,21 +450,22 @@
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         verifyDeviceNotNull(device, "setConnectionPolicy");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -477,17 +509,20 @@
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         verifyDeviceNotNull(device, "getConnectionPolicy");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
     /**
@@ -522,19 +557,18 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public void setVolume(int volume) {
         if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
-
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-                return;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setVolume(volume, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-
-            if (!isEnabled()) return;
-
-            service.setVolume(volume, mAttributionSource);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
         }
     }
 
@@ -555,24 +589,23 @@
             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
     })
     public long getHiSyncId(@NonNull BluetoothDevice device) {
-        if (VDBG) {
-            log("getHiSyncId(" + device + ")");
-        }
+        if (VDBG) log("getHiSyncId(" + device + ")");
         verifyDeviceNotNull(device, "getConnectionPolicy");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service == null) {
-                Log.w(TAG, "Proxy not attached to service");
-                return HI_SYNC_ID_INVALID;
+        final long defaultValue = HI_SYNC_ID_INVALID;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Long> recv = new SynchronousResultReceiver();
+                service.getHiSyncId(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-
-            if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
-
-            return service.getHiSyncId(device, mAttributionSource);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return HI_SYNC_ID_INVALID;
         }
+        return defaultValue;
     }
 
     /**
@@ -586,21 +619,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getDeviceSide(BluetoothDevice device) {
-        if (VDBG) {
-            log("getDeviceSide(" + device + ")");
-        }
+        if (VDBG) log("getDeviceSide(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getDeviceSide(device, mAttributionSource);
+        final int defaultValue = SIDE_LEFT;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getDeviceSide(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return SIDE_LEFT;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return SIDE_LEFT;
         }
+        return defaultValue;
     }
 
     /**
@@ -614,21 +648,22 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getDeviceMode(BluetoothDevice device) {
-        if (VDBG) {
-            log("getDeviceMode(" + device + ")");
-        }
+        if (VDBG) log("getDeviceMode(" + device + ")");
         final IBluetoothHearingAid service = getService();
-        try {
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                return service.getDeviceMode(device, mAttributionSource);
+        final int defaultValue = MODE_MONAURAL;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getDeviceMode(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return MODE_MONAURAL;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return MODE_MONAURAL;
         }
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index c2744b8..44a355b 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -24,17 +26,18 @@
 import android.annotation.SystemApi;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Provides the public APIs to control the Bluetooth HID Device profile.
@@ -432,7 +435,7 @@
                     "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) {
                 @Override
                 public IBluetoothHidDevice getServiceInterface(IBinder service) {
-                    return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHidDevice.Stub.asInterface(service);
                 }
     };
 
@@ -456,18 +459,23 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getConnectedDevices() {
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /** {@inheritDoc} */
@@ -476,19 +484,23 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /** {@inheritDoc} */
@@ -497,17 +509,20 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
     public int getConnectionState(BluetoothDevice device) {
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = STATE_DISCONNECTED;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -556,18 +571,21 @@
         }
 
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
-                result = service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = result;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
+                service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource, recv);
+                result = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -583,20 +601,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean unregisterApp() {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.unregisterApp(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.unregisterApp(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -610,20 +629,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.sendReport(device, id, data, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendReport(device, id, data, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -638,20 +658,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.replyReport(device, type, id, data, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.replyReport(device, type, id, data, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -664,20 +685,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean reportError(BluetoothDevice device, byte error) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.reportError(device, error, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.reportError(device, error, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -690,18 +712,20 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public String getUserAppName() {
         final IBluetoothHidDevice service = getService();
-
-        if (service != null) {
-            try {
-                return service.getUserAppName(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final String defaultValue = "";
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<String> recv = new SynchronousResultReceiver();
+                service.getUserAppName(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return "";
+        return defaultValue;
     }
 
     /**
@@ -715,20 +739,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(BluetoothDevice device) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -741,20 +766,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(BluetoothDevice device) {
-        boolean result = false;
-
         final IBluetoothHidDevice service = getService();
-        if (service != null) {
-            try {
-                result = service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-
-        return result;
+        return defaultValue;
     }
 
     /**
@@ -782,23 +808,24 @@
     })
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
-        log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothHidDevice service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothHidDevice service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index fb4cbb2..ecbeddf 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -16,26 +16,29 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 
 /**
@@ -245,7 +248,7 @@
                     "BluetoothHidHost", IBluetoothHidHost.class.getName()) {
                 @Override
                 public IBluetoothHidHost getServiceInterface(IBinder service) {
-                    return IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothHidHost.Stub.asInterface(service);
                 }
     };
 
@@ -293,16 +296,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -335,16 +342,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -359,17 +370,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -383,18 +400,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -412,16 +434,20 @@
             throw new IllegalArgumentException("device must not be null");
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -470,20 +496,22 @@
             throw new IllegalArgumentException("device must not be null");
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -529,16 +557,20 @@
             throw new IllegalArgumentException("device must not be null");
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
@@ -562,18 +594,20 @@
     public boolean virtualUnplug(BluetoothDevice device) {
         if (DBG) log("virtualUnplug(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.virtualUnplug(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.virtualUnplug(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
-
+        return defaultValue;
     }
 
     /**
@@ -589,16 +623,20 @@
     public boolean getProtocolMode(BluetoothDevice device) {
         if (VDBG) log("getProtocolMode(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getProtocolMode(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getProtocolMode(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -614,16 +652,20 @@
     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
         if (DBG) log("setProtocolMode(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.setProtocolMode(device, protocolMode, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setProtocolMode(device, protocolMode, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -646,17 +688,21 @@
                     + "bufferSize=" + bufferSize);
         }
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getReport(device, reportType, reportId, bufferSize,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getReport(device, reportType, reportId, bufferSize, mAttributionSource,
+                        recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -674,16 +720,20 @@
     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.setReport(device, reportType, report, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setReport(device, reportType, report, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -700,16 +750,20 @@
     public boolean sendData(BluetoothDevice device, String report) {
         if (DBG) log("sendData(" + device + "), report=" + report);
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendData(device, report, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendData(device, report, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -725,16 +779,20 @@
     public boolean getIdleTime(BluetoothDevice device) {
         if (DBG) log("getIdletime(" + device + ")");
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getIdleTime(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getIdleTime(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -751,16 +809,20 @@
     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
         final IBluetoothHidHost service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.setIdleTime(device, idleTime, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setIdleTime(device, idleTime, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index d7940eb..15db686 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -17,26 +17,28 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.SuppressLint;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the LeAudio profile.
@@ -333,7 +335,7 @@
                     IBluetoothLeAudio.class.getName()) {
                 @Override
                 public IBluetoothLeAudio getServiceInterface(IBinder service) {
-                    return IBluetoothLeAudio.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothLeAudio.Stub.asInterface(service);
                 }
     };
 
@@ -387,17 +389,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean connect(@Nullable BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
-                return service.connect(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -427,17 +433,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean disconnect(@Nullable BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled() && isValidDevice(device)) {
-                return service.disconnect(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -448,18 +458,24 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
+        final IBluetoothLeAudio service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -471,19 +487,24 @@
     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
             @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
+        final IBluetoothLeAudio service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<BluetoothDevice>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<BluetoothDevice>();
         }
+        return defaultValue;
     }
 
     /**
@@ -495,18 +516,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionState(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.STATE_DISCONNECTED;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.STATE_DISCONNECTED;
         }
+        return defaultValue;
     }
 
     /**
@@ -533,19 +557,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && ((device == null) || isValidDevice(device))) {
-                service.setActiveDevice(device, mAttributionSource);
-                return true;
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setActiveDevice(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -559,19 +585,25 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public List<BluetoothDevice> getActiveDevices() {
-        if (VDBG) log("getActiveDevices()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
+        if (VDBG) log("getActiveDevice()");
+        final IBluetoothLeAudio service = getService();
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getActiveDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getActiveDevices(mAttributionSource), mAttributionSource);
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return new ArrayList<>();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return new ArrayList<>();
         }
+        return defaultValue;
     }
 
     /**
@@ -585,17 +617,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public int getGroupId(@NonNull BluetoothDevice device) {
         if (VDBG) log("getGroupId()");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
-                return service.getGroupId(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final int defaultValue = GROUP_ID_INVALID;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getGroupId(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return GROUP_ID_INVALID;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return GROUP_ID_INVALID;
         }
+        return defaultValue;
     }
 
     /**
@@ -608,17 +644,18 @@
     @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void setVolume(int volume) {
         if (VDBG) log("setVolume(vol: " + volume + " )");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()) {
-                service.setVolume(volume, mAttributionSource);
-                return;
+        final IBluetoothLeAudio service = getService();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setVolume(volume, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return;
         }
     }
 
@@ -637,16 +674,20 @@
     public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) {
         if (VDBG) log("groupAddNode()");
         final IBluetoothLeAudio service = getService();
-        try {
-            if (service != null && mAdapter.isEnabled()) {
-                return service.groupAddNode(group_id, device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.groupAddNode(group_id, device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -665,16 +706,20 @@
     public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) {
         if (VDBG) log("groupRemoveNode()");
         final IBluetoothLeAudio service = getService();
-        try {
-            if (service != null && mAdapter.isEnabled()) {
-                return service.groupRemoveNode(group_id, device, mAttributionSource);
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.groupRemoveNode(group_id, device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -697,22 +742,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -730,18 +776,21 @@
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
-        try {
-            final IBluetoothLeAudio service = getService();
-            if (service != null && mAdapter.isEnabled()
-                    && isValidDevice(device)) {
-                return service.getConnectionPolicy(device, mAttributionSource);
+        final IBluetoothLeAudio service = getService();
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         }
+        return defaultValue;
     }
 
 
diff --git a/core/java/android/bluetooth/BluetoothLeBroadcast.java b/core/java/android/bluetooth/BluetoothLeBroadcast.java
new file mode 100644
index 0000000..fed9f91
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothLeBroadcast.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright 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.bluetooth;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth LE Broadcast Source profile.
+ *
+ * <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast
+ * Source Service via IPC. Use {@link BluetoothAdapter#getProfileProxy}
+ * to get the BluetoothLeBroadcast proxy object.
+ *
+ * @hide
+ */
+public final class BluetoothLeBroadcast implements BluetoothProfile {
+    private static final String TAG = "BluetoothLeBroadcast";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Constants used by the LE Audio Broadcast profile for the Broadcast state
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"LE_AUDIO_BROADCAST_STATE_"}, value = {
+      LE_AUDIO_BROADCAST_STATE_DISABLED,
+      LE_AUDIO_BROADCAST_STATE_ENABLING,
+      LE_AUDIO_BROADCAST_STATE_ENABLED,
+      LE_AUDIO_BROADCAST_STATE_DISABLING,
+      LE_AUDIO_BROADCAST_STATE_PLAYING,
+      LE_AUDIO_BROADCAST_STATE_NOT_PLAYING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LeAudioBroadcastState {}
+
+    /**
+     * Indicates that LE Audio Broadcast mode is currently disabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_DISABLED = 10;
+
+    /**
+     * Indicates that LE Audio Broadcast mode is being enabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_ENABLING = 11;
+
+    /**
+     * Indicates that LE Audio Broadcast mode is currently enabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_ENABLED = 12;
+    /**
+     * Indicates that LE Audio Broadcast mode is being disabled
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_DISABLING = 13;
+
+    /**
+     * Indicates that an LE Audio Broadcast mode is currently playing
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_PLAYING = 14;
+
+    /**
+     * Indicates that LE Audio Broadcast is currently not playing
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_STATE_NOT_PLAYING = 15;
+
+    /**
+     * Constants used by the LE Audio Broadcast profile for encryption key length
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"LE_AUDIO_BROADCAST_ENCRYPTION_KEY_"}, value = {
+      LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT,
+      LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LeAudioEncryptionKeyLength {}
+
+    /**
+     * Indicates that the LE Audio Broadcast encryption key size is 32 bits.
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT = 16;
+
+    /**
+     * Indicates that the LE Audio Broadcast encryption key size is 128 bits.
+     *
+     * @hide
+     */
+    public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT = 17;
+
+    /**
+     * Interface for receiving events related to broadcasts
+     */
+    public interface Callback {
+        /**
+         * Called when broadcast state has changed
+         *
+         * @param prevState broadcast state before the change
+         * @param newState broadcast state after the change
+         */
+        @LeAudioBroadcastState
+        void onBroadcastStateChange(int prevState, int newState);
+        /**
+         * Called when encryption key has been updated
+         *
+         * @param success true if the key was updated successfully, false otherwise
+         */
+        void onEncryptionKeySet(boolean success);
+    }
+
+    /**
+     * Create a BluetoothLeBroadcast proxy object for interacting with the local
+     * LE Audio Broadcast Source service.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothLeBroadcast(Context context,
+                                     BluetoothProfile.ServiceListener listener) {
+    }
+
+    /**
+     * Not supported since LE Audio Broadcasts do not establish a connection
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @hide
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        throw new UnsupportedOperationException(
+                   "LE Audio Broadcasts are not connection-oriented.");
+    }
+
+    /**
+     * Not supported since LE Audio Broadcasts do not establish a connection
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        throw new UnsupportedOperationException(
+                   "LE Audio Broadcasts are not connection-oriented.");
+    }
+
+    /**
+     * Not supported since LE Audio Broadcasts do not establish a connection
+     *
+     * @throws UnsupportedOperationException
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        throw new UnsupportedOperationException(
+                   "LE Audio Broadcasts are not connection-oriented.");
+    }
+
+    /**
+     * Enable LE Audio Broadcast mode.
+     *
+     * Generates a new broadcast ID and enables sending of encrypted or unencrypted
+     * isochronous PDUs
+     *
+     * @hide
+     */
+    public int enableBroadcastMode() {
+        if (DBG) log("enableBroadcastMode");
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED;
+    }
+
+    /**
+     * Disable LE Audio Broadcast mode.
+     *
+     * @hide
+     */
+    public int disableBroadcastMode() {
+        if (DBG) log("disableBroadcastMode");
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED;
+    }
+
+    /**
+     * Get the current LE Audio broadcast state
+     *
+     * @hide
+     */
+    @LeAudioBroadcastState
+    public int getBroadcastState() {
+        if (DBG) log("getBroadcastState");
+        return LE_AUDIO_BROADCAST_STATE_DISABLED;
+    }
+
+    /**
+     * Enable LE Audio broadcast encryption
+     *
+     * @param keyLength if useExisting is true, this specifies the length of the key that should
+     *                  be generated
+     * @param useExisting true, if an existing key should be used
+     *                    false, if a new key should be generated
+     *
+     * @hide
+     */
+    @LeAudioEncryptionKeyLength
+    public int enableEncryption(boolean useExisting, int keyLength) {
+        if (DBG) log("enableEncryption useExisting=" + useExisting + " keyLength=" + keyLength);
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED;
+    }
+
+    /**
+     * Disable LE Audio broadcast encryption
+     *
+     * @param removeExisting true, if the existing key should be removed
+     *                       false, otherwise
+     *
+     * @hide
+     */
+    public int disableEncryption(boolean removeExisting) {
+        if (DBG) log("disableEncryption removeExisting=" + removeExisting);
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED;
+    }
+
+    /**
+     * Enable or disable LE Audio broadcast encryption
+     *
+     * @param key use the provided key if non-null, generate a new key if null
+     * @param keyLength 0 if encryption is disabled, 4 bytes (low security),
+     *                  16 bytes (high security)
+     *
+     * @hide
+     */
+    @LeAudioEncryptionKeyLength
+    public int setEncryptionKey(byte[] key, int keyLength) {
+        if (DBG) log("setEncryptionKey key=" + key + " keyLength=" + keyLength);
+        return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED;
+    }
+
+
+    /**
+     * Get the encryption key that was set before
+     *
+     * @return encryption key as a byte array or null if no encryption key was set
+     *
+     * @hide
+     */
+    public byte[] getEncryptionKey() {
+        if (DBG) log("getEncryptionKey");
+        return null;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index 20152f3..fef6f22 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -16,17 +16,12 @@
 
 package android.bluetooth;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
-import android.app.ActivityThread;
-import android.app.AppGlobals;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -69,37 +64,11 @@
      * @hide
      */
     public BluetoothManager(Context context) {
-        mAttributionSource = resolveAttributionSource(context);
+        mAttributionSource = (context != null) ? context.getAttributionSource() :
+                AttributionSource.myAttributionSource();
         mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
     }
 
-    /** {@hide} */
-    public static @NonNull AttributionSource resolveAttributionSource(@Nullable Context context) {
-        AttributionSource res = null;
-        if (context != null) {
-            res = context.getAttributionSource();
-        }
-        if (res == null) {
-            res = ActivityThread.currentAttributionSource();
-        }
-        if (res == null) {
-            int uid = android.os.Process.myUid();
-            if (uid == android.os.Process.ROOT_UID) {
-                uid = android.os.Process.SYSTEM_UID;
-            }
-            try {
-                res = new AttributionSource.Builder(uid)
-                    .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
-                    .build();
-            } catch (RemoteException ignored) {
-            }
-        }
-        if (res == null) {
-            throw new IllegalStateException("Failed to resolve AttributionSource");
-        }
-        return res;
-    }
-
     /**
      * Get the BLUETOOTH Adapter for this device.
      *
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 8679651..56e4972 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -16,28 +16,31 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
-import android.annotation.SdkConstant.SdkConstantType;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth MAP
@@ -88,7 +91,7 @@
                     "BluetoothMap", IBluetoothMap.class.getName()) {
                 @Override
                 public IBluetoothMap getServiceInterface(IBinder service) {
-                    return IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothMap.Stub.asInterface(service);
                 }
     };
 
@@ -143,17 +146,20 @@
     public int getState() {
         if (VDBG) log("getState()");
         final IBluetoothMap service = getService();
-        if (service != null) {
-            try {
-                return service.getState(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = BluetoothMap.STATE_ERROR;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getState(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothMap.STATE_ERROR;
+        return defaultValue;
     }
 
     /**
@@ -169,18 +175,23 @@
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
         final IBluetoothMap service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getClient(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final BluetoothDevice defaultValue = null;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getClient(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -195,17 +206,20 @@
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null) {
-            try {
-                return service.isConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -234,16 +248,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -285,17 +303,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -310,18 +334,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -336,16 +365,21 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -391,20 +425,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                    && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -447,16 +483,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothMap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 042b586..03536f9a 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,19 +28,20 @@
 import android.app.PendingIntent;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth MAP MCE Profile.
@@ -181,7 +184,7 @@
                     "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
                 @Override
                 public IBluetoothMapClient getServiceInterface(IBinder service) {
-                    return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothMapClient.Stub.asInterface(service);
                 }
     };
 
@@ -222,17 +225,20 @@
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null) {
-            try {
-                return service.isConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -249,17 +255,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
         final IBluetoothMapClient service = getService();
-        if (service != null) {
-            try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -278,15 +287,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -301,17 +315,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) Log.d(TAG, "getConnectedDevices()");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /**
@@ -326,18 +346,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<>();
+        return defaultValue;
     }
 
     /**
@@ -352,16 +377,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue =  BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver<>();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -406,20 +435,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -461,16 +492,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     /**
@@ -495,18 +530,8 @@
     public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
             @NonNull String message, @Nullable PendingIntent sentIntent,
             @Nullable PendingIntent deliveredIntent) {
-        if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
-        final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]),
-                        message, sentIntent, deliveredIntent, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
-        return false;
+        return sendMessage(device, contacts.toArray(new Uri[contacts.size()]), message, sentIntent,
+                deliveredIntent);
     }
 
      /**
@@ -532,16 +557,21 @@
             PendingIntent sentIntent, PendingIntent deliveredIntent) {
         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
+                        mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -559,15 +589,20 @@
     public boolean getUnreadMessages(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getUnreadMessages(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.getUnreadMessages(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -581,13 +616,21 @@
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean isUploadingSupported(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "isUploadingSupported(" + device + ")");
         final IBluetoothMapClient service = getService();
-        try {
-            return (service != null && isEnabled() && isValidDevice(device))
-                    && ((service.getSupportedFeatures(device, mAttributionSource)
-                            & UPLOADING_FEATURE_BITMASK) > 0);
-        } catch (RemoteException e) {
-            Log.e(TAG, e.getMessage());
+        final int defaultValue = 0;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getSupportedFeatures(device, mAttributionSource, recv);
+                return (recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue)
+                        & UPLOADING_FEATURE_BITMASK) > 0;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
         return false;
     }
@@ -616,16 +659,21 @@
     public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
         if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
         final IBluetoothMapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device) && handle != null &&
-            (status == READ || status == UNREAD || status == UNDELETED  || status == DELETED)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device) && handle != null && (status == READ
+                    || status == UNREAD || status == UNDELETED  || status == DELETED)) {
             try {
-                return service.setMessageStatus(device, handle, status, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setMessageStatus(device, handle, status, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 577be3d..d4ad4ef4 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,7 +16,8 @@
 
 package android.bluetooth;
 
-import android.Manifest;
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
@@ -27,19 +28,20 @@
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth Pan
@@ -190,7 +192,7 @@
                     "BluetoothPan", IBluetoothPan.class.getName()) {
                 @Override
                 public IBluetoothPan getServiceInterface(IBinder service) {
-                    return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothPan.Stub.asInterface(service);
                 }
     };
 
@@ -251,16 +253,20 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -291,16 +297,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -324,22 +334,23 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
-        try {
-            final IBluetoothPan service = getService();
-            if (service != null && isEnabled()
-                    && isValidDevice(device)) {
-                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                    return false;
-                }
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
+        final IBluetoothPan service = getService();
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null) Log.w(TAG, "Proxy not attached to service");
-            return false;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-            return false;
         }
+        return defaultValue;
     }
 
     /**
@@ -356,17 +367,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -383,18 +400,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -411,16 +433,20 @@
     public int getConnectionState(@NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -440,11 +466,16 @@
         String pkgName = mContext.getOpPackageName();
         if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                service.setBluetoothTethering(value, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setBluetoothTethering(value, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
     }
@@ -461,14 +492,20 @@
     public boolean isTetheringOn() {
         if (VDBG) log("isTetheringOn()");
         final IBluetoothPan service = getService();
-        if (service != null && isEnabled()) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
-                return service.isTetheringOn(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isTetheringOn(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        return false;
+        return defaultValue;
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index c000e56..e137929 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -23,9 +23,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.Context;
@@ -170,7 +168,7 @@
                             mContext.getPackageManager(), 0);
                     intent.setComponent(comp);
                     if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                            UserHandle.CURRENT_OR_SELF)) {
+                            UserHandle.CURRENT)) {
                         Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
                         return false;
                     }
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index c7dd6bd..e096de8 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -16,23 +16,25 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth PBAP Client Profile.
@@ -66,7 +68,7 @@
                     "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) {
                 @Override
                 public IBluetoothPbapClient getServiceInterface(IBinder service) {
-                    return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothPbapClient.Stub.asInterface(service);
                 }
     };
 
@@ -125,18 +127,20 @@
             log("connect(" + device + ") for PBAP Client.");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.connect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.connect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -157,19 +161,21 @@
             log("disconnect(" + device + ")" + new Exception());
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                service.disconnect(device, mAttributionSource);
-                return true;
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
+        final boolean defaultValue = true;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+                return true;
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -186,19 +192,23 @@
             log("getConnectedDevices()");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -214,20 +224,23 @@
             log("getDevicesMatchingStates()");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled()) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
-                        mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -243,18 +256,20 @@
             log("getConnectionState(" + device + ")");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
-            }
-        }
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     private static void log(String msg) {
@@ -313,22 +328,22 @@
             log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
-            try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
-            }
-        }
+        final boolean defaultValue = false;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -372,17 +387,19 @@
             log("getConnectionPolicy(" + device + ")");
         }
         final IBluetoothPbapClient service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-            }
-        }
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 }
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 0cf9f9f..e047e5d 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -233,12 +233,19 @@
     int CSIP_SET_COORDINATOR = 25;
 
     /**
+     * LE Audio Broadcast Source
+     *
+     * @hide
+     */
+    int LE_AUDIO_BROADCAST = 26;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      *
      * @hide
      */
-    int MAX_PROFILE_ID = 25;
+    int MAX_PROFILE_ID = 26;
 
     /**
      * Default priority for devices that we try to auto-connect to and
@@ -436,6 +443,8 @@
                 return "OPP";
             case HEARING_AID:
                 return "HEARING_AID";
+            case LE_AUDIO:
+                return "LE_AUDIO";
             default:
                 return "UNKNOWN_PROFILE";
         }
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index a254291..ecd5e40 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -103,7 +103,7 @@
                             mContext.getPackageManager(), 0);
                     intent.setComponent(comp);
                     if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                            UserHandle.CURRENT_OR_SELF)) {
+                            UserHandle.CURRENT)) {
                         logError("Could not bind to Bluetooth Service with " + intent);
                         return false;
                     }
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index fda19ed..808fa39 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
@@ -24,17 +26,18 @@
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the APIs to control the Bluetooth SIM
@@ -105,7 +108,7 @@
                     "BluetoothSap", IBluetoothSap.class.getName()) {
                 @Override
                 public IBluetoothSap getServiceInterface(IBinder service) {
-                    return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothSap.Stub.asInterface(service);
                 }
     };
 
@@ -156,17 +159,20 @@
     public int getState() {
         if (VDBG) log("getState()");
         final IBluetoothSap service = getService();
-        if (service != null) {
-            try {
-                return service.getState(mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final int defaultValue = BluetoothSap.STATE_ERROR;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getState(mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return BluetoothSap.STATE_ERROR;
+        return defaultValue;
     }
 
     /**
@@ -181,18 +187,23 @@
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
         final IBluetoothSap service = getService();
-        if (service != null) {
-            try {
-                return Attributable.setAttributionSource(
-                        service.getClient(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final BluetoothDevice defaultValue = null;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<BluetoothDevice> recv =
+                        new SynchronousResultReceiver();
+                service.getClient(mAttributionSource, recv);
+                return Attributable.setAttributionSource(
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return null;
+        return defaultValue;
     }
 
     /**
@@ -207,17 +218,20 @@
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null) {
-            try {
-                return service.isConnected(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
+        final boolean defaultValue = false;
+        if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.isConnected(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+            }
         }
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -245,16 +259,20 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.disconnect(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.disconnect(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -268,17 +286,23 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -292,18 +316,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -317,16 +346,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -371,20 +404,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -426,16 +461,20 @@
     public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothSap service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private static void log(String msg) {
diff --git a/core/java/android/bluetooth/BluetoothStatusCodes.java b/core/java/android/bluetooth/BluetoothStatusCodes.java
index 5ba7bb1..fff32ff 100644
--- a/core/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/core/java/android/bluetooth/BluetoothStatusCodes.java
@@ -226,6 +226,66 @@
     public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109;
 
     /**
+     * Indicates that setting the LE Audio Broadcast mode failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED = 1110;
+
+    /**
+     * Indicates that setting a new encryption key for Bluetooth LE Audio Broadcast Source failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED = 1111;
+
+    /**
+     * Indicates that connecting to a remote Broadcast Audio Scan Service failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_CONNECT_FAILED = 1112;
+
+    /**
+     * Indicates that disconnecting from a remote Broadcast Audio Scan Service failed.
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_DISCONNECT_FAILED = 1113;
+
+    /**
+     * Indicates that enabling LE Audio Broadcast encryption failed
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED = 1114;
+
+    /**
+     * Indicates that disabling LE Audio Broadcast encryption failed
+     * <p>
+     * Example solution: Change parameters and try again. If error persists, the app can report
+     * telemetry and/or log the error in a bugreport.
+     *
+     * @hide
+     */
+    public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115;
+
+    /**
      * Indicates that an unknown error has occurred has occurred.
      */
     public static final int ERROR_UNKNOWN = Integer.MAX_VALUE;
diff --git a/core/java/android/bluetooth/BluetoothUtils.java b/core/java/android/bluetooth/BluetoothUtils.java
new file mode 100644
index 0000000..8674692
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothUtils.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bluetooth;
+
+import java.time.Duration;
+
+/**
+ * {@hide}
+ */
+public final class BluetoothUtils {
+    /**
+     * This utility class cannot be instantiated
+     */
+    private BluetoothUtils() {}
+
+    /**
+     * Timeout value for synchronous binder call
+     */
+    private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(5);
+
+    /**
+     * @return timeout value for synchronous binder call
+     */
+    static Duration getSyncTimeout() {
+        return SYNC_CALLS_TIMEOUT;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java
index 678c11a..27532aa 100644
--- a/core/java/android/bluetooth/BluetoothVolumeControl.java
+++ b/core/java/android/bluetooth/BluetoothVolumeControl.java
@@ -17,6 +17,8 @@
 
 package android.bluetooth;
 
+import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+
 import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -27,18 +29,18 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.content.Context;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.CloseGuard;
 import android.util.Log;
 
+import com.android.modules.utils.SynchronousResultReceiver;
+
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class provides the public APIs to control the Bluetooth Volume Control service.
@@ -88,7 +90,7 @@
                     IBluetoothVolumeControl.class.getName()) {
                 @Override
                 public IBluetoothVolumeControl getServiceInterface(IBinder service) {
-                    return IBluetoothVolumeControl.Stub.asInterface(Binder.allowBlocking(service));
+                    return IBluetoothVolumeControl.Stub.asInterface(service);
                 }
             };
 
@@ -136,17 +138,23 @@
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getConnectedDevices(mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getConnectedDevices(mAttributionSource), mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
+                        mAttributionSource);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -161,18 +169,23 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled()) {
+        final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
             try {
+                final SynchronousResultReceiver<List<BluetoothDevice>> recv =
+                        new SynchronousResultReceiver();
+                service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
                 return Attributable.setAttributionSource(
-                        service.getDevicesMatchingConnectionStates(states, mAttributionSource),
+                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
                         mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        return defaultValue;
     }
 
     /**
@@ -187,16 +200,20 @@
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionState(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.STATE_DISCONNECTED;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionState(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.STATE_DISCONNECTED;
+        return defaultValue;
     }
 
     /**
@@ -214,18 +231,19 @@
     })
     public void setVolume(@Nullable BluetoothDevice device,
             @IntRange(from = 0, to = 255) int volume) {
-        if (DBG)
-            log("setVolume(" + volume + ")");
+        if (DBG) log("setVolume(" + volume + ")");
         final IBluetoothVolumeControl service = getService();
-        try {
-            if (service != null && isEnabled()) {
-                service.setVolume(device, volume, mAttributionSource);
-                return;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled()) {
+            try {
+                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
+                service.setVolume(device, volume, mAttributionSource, recv);
+                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
-            if (service == null)
-                Log.w(TAG, "Proxy not attached to service");
-        } catch (RemoteException e) {
-            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
         }
     }
 
@@ -251,20 +269,22 @@
             @ConnectionPolicy int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
-                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                return false;
-            }
+        final boolean defaultValue = false;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)
+                && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
             try {
-                return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return false;
+                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
+                service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return false;
+        return defaultValue;
     }
 
     /**
@@ -287,16 +307,20 @@
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothVolumeControl service = getService();
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        } else if (isEnabled() && isValidDevice(device)) {
             try {
-                return service.getConnectionPolicy(device, mAttributionSource);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
+                service.getConnectionPolicy(device, mAttributionSource, recv);
+                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+            } catch (RemoteException | TimeoutException e) {
+                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
             }
         }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        return defaultValue;
     }
 
     private boolean isEnabled() {
diff --git a/core/java/android/bluetooth/BufferConstraints.java b/core/java/android/bluetooth/BufferConstraints.java
index 97d9723..06b45ee 100644
--- a/core/java/android/bluetooth/BufferConstraints.java
+++ b/core/java/android/bluetooth/BufferConstraints.java
@@ -55,7 +55,7 @@
     BufferConstraints(Parcel in) {
         mBufferConstraintList = new ArrayList<BufferConstraint>();
         mBufferConstraints = new HashMap<Integer, BufferConstraint>();
-        in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader());
+        in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader(), android.bluetooth.BufferConstraint.class);
         for (int i = 0; i < mBufferConstraintList.size(); i++) {
             mBufferConstraints.put(i, mBufferConstraintList.get(i));
         }
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index ee173db..540e5a7 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -23,6 +23,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.app.PendingIntent;
+import android.bluetooth.Attributable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothGatt;
 import android.bluetooth.IBluetoothGatt;
@@ -30,7 +31,6 @@
 import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
 import android.bluetooth.annotations.RequiresBluetoothScanPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.os.Handler;
 import android.os.Looper;
@@ -514,16 +514,27 @@
         @Override
         public void onScanResult(final ScanResult scanResult) {
             Attributable.setAttributionSource(scanResult, mAttributionSource);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId);
+            }
             if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
 
             // Check null in case the scan has been stopped
             synchronized (this) {
-                if (mScannerId <= 0) return;
+                if (mScannerId <= 0) {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Ignoring result as scan stopped.");
+                    }
+                    return;
+                };
             }
             Handler handler = new Handler(Looper.getMainLooper());
             handler.post(new Runnable() {
                 @Override
                 public void run() {
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "onScanResult() - handler run");
+                    }
                     mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
                 }
             });
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
index dea686d..bbd3117 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
+import android.bluetooth.Attributable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.IBluetoothGatt;
@@ -25,7 +26,6 @@
 import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
 import android.bluetooth.annotations.RequiresBluetoothScanPermission;
 import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.os.Handler;
 import android.os.Looper;
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index b059193..675fe05 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -200,28 +200,28 @@
                 address = in.readString();
             }
             if (in.readInt() == 1) {
-                ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
+                ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
                 builder.setServiceUuid(uuid);
                 if (in.readInt() == 1) {
                     ParcelUuid uuidMask = in.readParcelable(
-                            ParcelUuid.class.getClassLoader());
+                            ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
                     builder.setServiceUuid(uuid, uuidMask);
                 }
             }
             if (in.readInt() == 1) {
                 ParcelUuid solicitationUuid = in.readParcelable(
-                        ParcelUuid.class.getClassLoader());
+                        ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
                 builder.setServiceSolicitationUuid(solicitationUuid);
                 if (in.readInt() == 1) {
                     ParcelUuid solicitationUuidMask = in.readParcelable(
-                            ParcelUuid.class.getClassLoader());
+                            ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
                     builder.setServiceSolicitationUuid(solicitationUuid,
                             solicitationUuidMask);
                 }
             }
             if (in.readInt() == 1) {
                 ParcelUuid servcieDataUuid =
-                        in.readParcelable(ParcelUuid.class.getClassLoader());
+                        in.readParcelable(ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
                 if (in.readInt() == 1) {
                     int serviceDataLength = in.readInt();
                     byte[] serviceData = new byte[serviceDataLength];
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
index 5228456..f437d86 100644
--- a/core/java/android/bluetooth/le/ScanResult.java
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -18,8 +18,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.bluetooth.Attributable;
 import android.bluetooth.BluetoothDevice;
-import android.content.Attributable;
 import android.content.AttributionSource;
 import android.os.Parcel;
 import android.os.Parcelable;
diff --git a/core/java/android/bluetooth/le/TransportBlock.java b/core/java/android/bluetooth/le/TransportBlock.java
index b388bed..18bad9c 100644
--- a/core/java/android/bluetooth/le/TransportBlock.java
+++ b/core/java/android/bluetooth/le/TransportBlock.java
@@ -24,6 +24,7 @@
 
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
 
 /**
  * Wrapper for Transport Discovery Data Transport Blocks.
@@ -59,8 +60,12 @@
         mOrgId = in.readInt();
         mTdsFlags = in.readInt();
         mTransportDataLength = in.readInt();
-        mTransportData = new byte[mTransportDataLength];
-        in.readByteArray(mTransportData);
+        if (mTransportDataLength > 0) {
+            mTransportData = new byte[mTransportDataLength];
+            in.readByteArray(mTransportData);
+        } else {
+            mTransportData = null;
+        }
     }
 
     @Override
@@ -68,7 +73,9 @@
         dest.writeInt(mOrgId);
         dest.writeInt(mTdsFlags);
         dest.writeInt(mTransportDataLength);
-        dest.writeByteArray(mTransportData);
+        if (mTransportData != null) {
+            dest.writeByteArray(mTransportData);
+        }
     }
 
     /**
@@ -79,6 +86,21 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        TransportBlock other = (TransportBlock) obj;
+        return Arrays.equals(toByteArray(), other.toByteArray());
+    }
+
     public static final @NonNull Creator<TransportBlock> CREATOR = new Creator<TransportBlock>() {
         @Override
         public TransportBlock createFromParcel(Parcel in) {
diff --git a/core/java/android/bluetooth/le/TransportDiscoveryData.java b/core/java/android/bluetooth/le/TransportDiscoveryData.java
index c8e97f9..2b52f19 100644
--- a/core/java/android/bluetooth/le/TransportDiscoveryData.java
+++ b/core/java/android/bluetooth/le/TransportDiscoveryData.java
@@ -26,6 +26,7 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -96,6 +97,21 @@
         return 0;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        TransportDiscoveryData other = (TransportDiscoveryData) obj;
+        return Arrays.equals(toByteArray(), other.toByteArray());
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mTransportDataType);
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index 6e1f8b5..1d2f06d 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -20,12 +20,15 @@
 
 import static com.android.internal.util.CollectionUtils.emptyIfNull;
 
+import static java.util.Objects.requireNonNull;
+
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.StringDef;
 import android.annotation.SystemApi;
+import android.annotation.UserIdInt;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -52,12 +55,11 @@
  * device to be shown instead of a list to choose from
  */
 @DataClass(
+        genConstructor = false,
         genToString = true,
         genEqualsHashCode = true,
         genHiddenGetters = true,
         genParcelable = true,
-        genHiddenConstructor = true,
-        genBuilder = false,
         genConstDefs = false)
 public final class AssociationRequest implements Parcelable {
     /**
@@ -151,40 +153,76 @@
     private final boolean mForceConfirmation;
 
     /**
-     * The app package making the request.
-     *
+     * The app package name of the application the association will belong to.
      * Populated by the system.
-     *
      * @hide
      */
-    private @Nullable String mCallingPackage;
+    private @Nullable String mPackageName;
+
+    /**
+     * The UserId of the user the association will belong to.
+     * Populated by the system.
+     * @hide
+     */
+    private @UserIdInt int mUserId;
 
     /**
      * The user-readable description of the device profile's privileges.
-     *
      * Populated by the system.
-     *
      * @hide
      */
     private @Nullable String mDeviceProfilePrivilegesDescription;
 
     /**
      * The time at which his request was created
-     *
      * @hide
      */
-    private long mCreationTime;
+    private final long mCreationTime;
 
     /**
      * Whether the user-prompt may be skipped once the device is found.
-     *
      * Populated by the system.
-     *
      * @hide
      */
     private boolean mSkipPrompt;
 
     /**
+     * Creates a new AssociationRequest.
+     *
+     * @param singleDevice
+     *   Whether only a single device should match the provided filter.
+     *
+     *   When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
+     *   address, bonded devices are also searched among. This allows to obtain the necessary app
+     *   privileges even if the device is already paired.
+     * @param deviceFilters
+     *   If set, only devices matching either of the given filters will be shown to the user
+     * @param deviceProfile
+     *   Profile of the device.
+     * @param displayName
+     *   The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
+     *   "self-managed" association.
+     * @param selfManaged
+     *   Whether the association is to be managed by the companion application.
+     */
+    private AssociationRequest(
+            boolean singleDevice,
+            @NonNull List<DeviceFilter<?>> deviceFilters,
+            @Nullable @DeviceProfile String deviceProfile,
+            @Nullable CharSequence displayName,
+            boolean selfManaged,
+            boolean forceConfirmation) {
+        mSingleDevice = singleDevice;
+        mDeviceFilters = requireNonNull(deviceFilters);
+        mDeviceProfile = deviceProfile;
+        mDisplayName = displayName;
+        mSelfManaged = selfManaged;
+        mForceConfirmation = forceConfirmation;
+
+        mCreationTime = System.currentTimeMillis();
+    }
+
+    /**
      * @return profile of the companion device.
      */
     public @Nullable @DeviceProfile String getDeviceProfile() {
@@ -237,8 +275,13 @@
     }
 
     /** @hide */
-    public void setCallingPackage(@NonNull String pkg) {
-        mCallingPackage = pkg;
+    public void setPackageName(@NonNull String packageName) {
+        mPackageName = packageName;
+    }
+
+    /** @hide */
+    public void setUserId(@UserIdInt int userId) {
+        mUserId = userId;
     }
 
     /** @hide */
@@ -248,7 +291,7 @@
 
     /** @hide */
     public void setSkipPrompt(boolean value) {
-        mSkipPrompt = true;
+        mSkipPrompt = value;
     }
 
     /** @hide */
@@ -258,10 +301,6 @@
         return mDeviceFilters;
     }
 
-    private void onConstructed() {
-        mCreationTime = System.currentTimeMillis();
-    }
-
     /**
      * A builder for {@link AssociationRequest}
      */
@@ -325,7 +364,7 @@
         @NonNull
         public Builder setDisplayName(@NonNull CharSequence displayName) {
             checkNotUsed();
-            mDisplayName = Objects.requireNonNull(displayName);
+            mDisplayName = requireNonNull(displayName);
             return this;
         }
 
@@ -372,15 +411,13 @@
                         + "provide the display name of the device");
             }
             return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
-                    mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation,
-                    null, null, -1L, false);
+                    mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation);
         }
     }
 
 
 
 
-
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
@@ -395,88 +432,29 @@
 
 
     /**
-     * Creates a new AssociationRequest.
-     *
-     * @param singleDevice
-     *   Whether only a single device should match the provided filter.
-     *
-     *   When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
-     *   address, bonded devices are also searched among. This allows to obtain the necessary app
-     *   privileges even if the device is already paired.
-     * @param deviceFilters
-     *   If set, only devices matching either of the given filters will be shown to the user
-     * @param deviceProfile
-     *   Profile of the device.
-     * @param displayName
-     *   The Display name of the device to be shown in the CDM confirmation UI. Must be non-null for
-     *   "self-managed" association.
-     * @param selfManaged
-     *   Whether the association is to be managed by the companion application.
-     * @param forceConfirmation
-     *   Indicates that the application would prefer the CompanionDeviceManager to collect an explicit
-     *   confirmation from the user before creating an association, even if such confirmation is not
-     *   required.
-     * @param callingPackage
-     *   The app package making the request.
-     *
-     *   Populated by the system.
-     * @param deviceProfilePrivilegesDescription
-     *   The user-readable description of the device profile's privileges.
-     *
-     *   Populated by the system.
-     * @param creationTime
-     *   The time at which his request was created
-     * @param skipPrompt
-     *   Whether the user-prompt may be skipped once the device is found.
-     *
-     *   Populated by the system.
-     * @hide
-     */
-    @DataClass.Generated.Member
-    public AssociationRequest(
-            boolean singleDevice,
-            @NonNull List<DeviceFilter<?>> deviceFilters,
-            @Nullable @DeviceProfile String deviceProfile,
-            @Nullable CharSequence displayName,
-            boolean selfManaged,
-            boolean forceConfirmation,
-            @Nullable String callingPackage,
-            @Nullable String deviceProfilePrivilegesDescription,
-            long creationTime,
-            boolean skipPrompt) {
-        this.mSingleDevice = singleDevice;
-        this.mDeviceFilters = deviceFilters;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mDeviceFilters);
-        this.mDeviceProfile = deviceProfile;
-        com.android.internal.util.AnnotationValidations.validate(
-                DeviceProfile.class, null, mDeviceProfile);
-        this.mDisplayName = displayName;
-        this.mSelfManaged = selfManaged;
-        this.mForceConfirmation = forceConfirmation;
-        this.mCallingPackage = callingPackage;
-        this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
-        this.mCreationTime = creationTime;
-        this.mSkipPrompt = skipPrompt;
-
-        onConstructed();
-    }
-
-    /**
-     * The app package making the request.
-     *
+     * The app package name of the application the association will belong to.
      * Populated by the system.
      *
      * @hide
      */
     @DataClass.Generated.Member
-    public @Nullable String getCallingPackage() {
-        return mCallingPackage;
+    public @Nullable String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * The UserId of the user the association will belong to.
+     * Populated by the system.
+     *
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public @UserIdInt int getUserId() {
+        return mUserId;
     }
 
     /**
      * The user-readable description of the device profile's privileges.
-     *
      * Populated by the system.
      *
      * @hide
@@ -498,7 +476,6 @@
 
     /**
      * Whether the user-prompt may be skipped once the device is found.
-     *
      * Populated by the system.
      *
      * @hide
@@ -521,7 +498,8 @@
                 "displayName = " + mDisplayName + ", " +
                 "selfManaged = " + mSelfManaged + ", " +
                 "forceConfirmation = " + mForceConfirmation + ", " +
-                "callingPackage = " + mCallingPackage + ", " +
+                "packageName = " + mPackageName + ", " +
+                "userId = " + mUserId + ", " +
                 "deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription + ", " +
                 "creationTime = " + mCreationTime + ", " +
                 "skipPrompt = " + mSkipPrompt +
@@ -547,7 +525,8 @@
                 && Objects.equals(mDisplayName, that.mDisplayName)
                 && mSelfManaged == that.mSelfManaged
                 && mForceConfirmation == that.mForceConfirmation
-                && Objects.equals(mCallingPackage, that.mCallingPackage)
+                && Objects.equals(mPackageName, that.mPackageName)
+                && mUserId == that.mUserId
                 && Objects.equals(mDeviceProfilePrivilegesDescription, that.mDeviceProfilePrivilegesDescription)
                 && mCreationTime == that.mCreationTime
                 && mSkipPrompt == that.mSkipPrompt;
@@ -566,7 +545,8 @@
         _hash = 31 * _hash + Objects.hashCode(mDisplayName);
         _hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
         _hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
-        _hash = 31 * _hash + Objects.hashCode(mCallingPackage);
+        _hash = 31 * _hash + Objects.hashCode(mPackageName);
+        _hash = 31 * _hash + mUserId;
         _hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
         _hash = 31 * _hash + Long.hashCode(mCreationTime);
         _hash = 31 * _hash + Boolean.hashCode(mSkipPrompt);
@@ -583,16 +563,17 @@
         if (mSingleDevice) flg |= 0x1;
         if (mSelfManaged) flg |= 0x10;
         if (mForceConfirmation) flg |= 0x20;
-        if (mSkipPrompt) flg |= 0x200;
+        if (mSkipPrompt) flg |= 0x400;
         if (mDeviceProfile != null) flg |= 0x4;
         if (mDisplayName != null) flg |= 0x8;
-        if (mCallingPackage != null) flg |= 0x40;
-        if (mDeviceProfilePrivilegesDescription != null) flg |= 0x80;
+        if (mPackageName != null) flg |= 0x40;
+        if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
         dest.writeInt(flg);
         dest.writeParcelableList(mDeviceFilters, flags);
         if (mDeviceProfile != null) dest.writeString(mDeviceProfile);
         if (mDisplayName != null) dest.writeCharSequence(mDisplayName);
-        if (mCallingPackage != null) dest.writeString(mCallingPackage);
+        if (mPackageName != null) dest.writeString(mPackageName);
+        dest.writeInt(mUserId);
         if (mDeviceProfilePrivilegesDescription != null) dest.writeString(mDeviceProfilePrivilegesDescription);
         dest.writeLong(mCreationTime);
     }
@@ -612,13 +593,14 @@
         boolean singleDevice = (flg & 0x1) != 0;
         boolean selfManaged = (flg & 0x10) != 0;
         boolean forceConfirmation = (flg & 0x20) != 0;
-        boolean skipPrompt = (flg & 0x200) != 0;
+        boolean skipPrompt = (flg & 0x400) != 0;
         List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
-        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader());
+        in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(), (Class<android.companion.DeviceFilter<?>>) (Class<?>) android.companion.DeviceFilter.class);
         String deviceProfile = (flg & 0x4) == 0 ? null : in.readString();
         CharSequence displayName = (flg & 0x8) == 0 ? null : (CharSequence) in.readCharSequence();
-        String callingPackage = (flg & 0x40) == 0 ? null : in.readString();
-        String deviceProfilePrivilegesDescription = (flg & 0x80) == 0 ? null : in.readString();
+        String packageName = (flg & 0x40) == 0 ? null : in.readString();
+        int userId = in.readInt();
+        String deviceProfilePrivilegesDescription = (flg & 0x100) == 0 ? null : in.readString();
         long creationTime = in.readLong();
 
         this.mSingleDevice = singleDevice;
@@ -631,12 +613,15 @@
         this.mDisplayName = displayName;
         this.mSelfManaged = selfManaged;
         this.mForceConfirmation = forceConfirmation;
-        this.mCallingPackage = callingPackage;
+        this.mPackageName = packageName;
+        this.mUserId = userId;
+        com.android.internal.util.AnnotationValidations.validate(
+                UserIdInt.class, null, mUserId);
         this.mDeviceProfilePrivilegesDescription = deviceProfilePrivilegesDescription;
         this.mCreationTime = creationTime;
         this.mSkipPrompt = skipPrompt;
 
-        onConstructed();
+        // onConstructed(); // You can define this method to get a callback
     }
 
     @DataClass.Generated.Member
@@ -654,10 +639,10 @@
     };
 
     @DataClass.Generated(
-            time = 1638368698639L,
+            time = 1638962248060L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/companion/AssociationRequest.java",
-            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate final  boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final  boolean mSelfManaged\nprivate final  boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mCallingPackage\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate  long mCreationTime\nprivate  boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic  boolean isSingleDevice()\npublic  void setCallingPackage(java.lang.String)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic  void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nprivate  void onConstructed()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate  boolean mSelfManaged\nprivate  boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genHiddenConstructor=true, genBuilder=false, genConstDefs=false)")
+            inputSignatures = "public static final  java.lang.String DEVICE_PROFILE_WATCH\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_APP_STREAMING\npublic static final @android.annotation.RequiresPermission @android.annotation.SystemApi java.lang.String DEVICE_PROFILE_AUTOMOTIVE_PROJECTION\nprivate final  boolean mSingleDevice\nprivate final @com.android.internal.util.DataClass.PluralOf(\"deviceFilter\") @android.annotation.NonNull java.util.List<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate final @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String mDeviceProfile\nprivate final @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate final  boolean mSelfManaged\nprivate final  boolean mForceConfirmation\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.UserIdInt int mUserId\nprivate @android.annotation.Nullable java.lang.String mDeviceProfilePrivilegesDescription\nprivate final  long mCreationTime\nprivate  boolean mSkipPrompt\npublic @android.annotation.Nullable @android.companion.AssociationRequest.DeviceProfile java.lang.String getDeviceProfile()\npublic @android.annotation.Nullable java.lang.CharSequence getDisplayName()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isSelfManaged()\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission boolean isForceConfirmation()\npublic  boolean isSingleDevice()\npublic  void setPackageName(java.lang.String)\npublic  void setUserId(int)\npublic  void setDeviceProfilePrivilegesDescription(java.lang.String)\npublic  void setSkipPrompt(boolean)\npublic @android.annotation.NonNull @android.compat.annotation.UnsupportedAppUsage java.util.List<android.companion.DeviceFilter<?>> getDeviceFilters()\nclass AssociationRequest extends java.lang.Object implements [android.os.Parcelable]\nprivate  boolean mSingleDevice\nprivate @android.annotation.Nullable java.util.ArrayList<android.companion.DeviceFilter<?>> mDeviceFilters\nprivate @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate @android.annotation.Nullable java.lang.CharSequence mDisplayName\nprivate  boolean mSelfManaged\nprivate  boolean mForceConfirmation\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setSingleDevice(boolean)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder addDeviceFilter(android.companion.DeviceFilter<?>)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDeviceProfile(java.lang.String)\npublic @android.annotation.NonNull android.companion.AssociationRequest.Builder setDisplayName(java.lang.CharSequence)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setSelfManaged(boolean)\npublic @android.annotation.SystemApi @android.annotation.RequiresPermission @android.annotation.NonNull android.companion.AssociationRequest.Builder setForceConfirmation(boolean)\npublic @android.annotation.NonNull @java.lang.Override android.companion.AssociationRequest build()\nclass Builder extends android.provider.OneTimeUseBuilder<android.companion.AssociationRequest> implements []\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genEqualsHashCode=true, genHiddenGetters=true, genParcelable=true, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/companion/BluetoothDeviceFilter.java b/core/java/android/companion/BluetoothDeviceFilter.java
index be663f7..e0018f4 100644
--- a/core/java/android/companion/BluetoothDeviceFilter.java
+++ b/core/java/android/companion/BluetoothDeviceFilter.java
@@ -70,7 +70,7 @@
     }
 
     private static List<ParcelUuid> readUuids(Parcel in) {
-        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader());
+        return in.readParcelableList(new ArrayList<>(), ParcelUuid.class.getClassLoader(), android.os.ParcelUuid.class);
     }
 
     /** @hide */
diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java
index 58898cc..e6091f0 100644
--- a/core/java/android/companion/BluetoothLeDeviceFilter.java
+++ b/core/java/android/companion/BluetoothLeDeviceFilter.java
@@ -252,7 +252,7 @@
         public BluetoothLeDeviceFilter createFromParcel(Parcel in) {
             Builder builder = new Builder()
                     .setNamePattern(patternFromString(in.readString()))
-                    .setScanFilter(in.readParcelable(null));
+                    .setScanFilter(in.readParcelable(null, android.bluetooth.le.ScanFilter.class));
             byte[] rawDataFilter = in.createByteArray();
             byte[] rawDataFilterMask = in.createByteArray();
             if (rawDataFilter != null) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2b12f12..ae13425 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -776,6 +776,51 @@
         }
     }
 
+    /**
+     * Notify the system that the given self-managed association has just 'appeared'.
+     * This causes the system to bind to the companion app to keep it running until the association
+     * is reported as 'disappeared'
+     *
+     * <p>This API is only available for the companion apps that manage the connectivity by
+     * themselves.</p>
+     *
+     * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association
+     * recorded by CompanionDeviceManager
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
+    public void notifyDeviceAppeared(int associationId) {
+        try {
+            mService.notifyDeviceAppeared(associationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Notify the system that the given self-managed association has just 'disappeared'.
+     * This causes the system to unbind to the companion app.
+     *
+     * <p>This API is only available for the companion apps that manage the connectivity by
+     * themselves.</p>
+     *
+     * @param associationId the unique {@link AssociationInfo#getId ID} assigned to the Association
+     * recorded by CompanionDeviceManager
+
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
+    public void notifyDeviceDisappeared(int associationId) {
+        try {
+            mService.notifyDeviceDisappeared(associationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private boolean checkFeaturePresent() {
         boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 15266d6..3237f7c 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -21,12 +21,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
 import android.app.Service;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
 import android.util.Log;
 
+
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.Objects;
@@ -34,8 +36,9 @@
 /**
  * Service to be implemented by apps that manage a companion device.
  *
- * System will keep this service bound whenever an associated device is nearby,
- * ensuring app stays alive.
+ * System will keep this service bound whenever an associated device is nearby for Bluetooth
+ * devices or companion app manages the connectivity and reports disappeared, ensuring app stays
+ * alive
  *
  * An app must be {@link CompanionDeviceManager#associate associated} with at leas one device,
  * before it can take advantage of this service.
@@ -43,6 +46,17 @@
  * You must declare this service in your manifest with an
  * intent-filter action of {@link #SERVICE_INTERFACE} and
  * permission of {@link android.Manifest.permission#BIND_COMPANION_DEVICE_SERVICE}
+ *
+ * <p>If you want to declare more than one of these services, you must declare the meta-data in the
+ * service of your manifest with the corresponding name and value to true to indicate the
+ * primary service.
+ * Only the primary one will get the callback from
+ * {@link #onDeviceAppeared(AssociationInfo associationInfo)}.</p>
+ *
+ * Example:
+ * <meta-data
+ *   android:name="primary"
+ *   android:value="true" />
  */
 public abstract class CompanionDeviceService extends Service {
 
@@ -52,13 +66,14 @@
      * An intent action for a service to be bound whenever this app's companion device(s)
      * are nearby.
      *
-     * <p>The app will be kept alive for as long as the device is nearby.
+     * <p>The app will be kept alive for as long as the device is nearby or companion app reports
+     * appeared.
      * If the app is not running at the time device gets connected, the app will be woken up.</p>
      *
-     * <p>Shortly after the device goes out of range, the service will be unbound, and the
-     * app will be eligible for cleanup, unless any other user-visible components are running.</p>
+     * <p>Shortly after the device goes out of range or the companion app reports disappeared,
+     * the service will be unbound, and the app will be eligible for cleanup, unless any other
+     * user-visible components are running.</p>
      *
-     * <p>An app shouldn't declare more than one of these services.
      * If running in background is not essential for the devices that this app can manage,
      * app should avoid declaring this service.</p>
      *
@@ -73,9 +88,13 @@
      * Called by system whenever a device associated with this app is available.
      *
      * @param address the MAC address of the device
+     * @deprecated please override {@link #onDeviceAppeared(AssociationInfo)} instead.
      */
+    @Deprecated
     @MainThread
-    public abstract void onDeviceAppeared(@NonNull String address);
+    public void onDeviceAppeared(@NonNull String address) {
+        // Do nothing. Companion apps can override this function.
+    }
 
     /**
      * Called by system whenever a device associated with this app stops being available.
@@ -83,9 +102,13 @@
      * Usually this means the device goes out of range or is turned off.
      *
      * @param address the MAC address of the device
+     * @deprecated please override {@link #onDeviceDisappeared(AssociationInfo)} instead.
      */
+    @Deprecated
     @MainThread
-    public abstract void onDeviceDisappeared(@NonNull String address);
+    public void onDeviceDisappeared(@NonNull String address) {
+        // Do nothing. Companion apps can override this function.
+    }
 
     /**
      * Called by system whenever the system dispatches a message to the app to send it to
@@ -118,10 +141,35 @@
         companionDeviceManager.dispatchMessage(messageId, associationId, message);
     }
 
+    /**
+     * Called by system whenever a device associated with this app is connected.
+     *
+     * @param associationInfo A record for the companion device.
+     */
+    @MainThread
+    public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) {
+        if (!associationInfo.isSelfManaged()) {
+            onDeviceAppeared(associationInfo.getDeviceMacAddressAsString());
+        }
+    }
+
+    /**
+     * Called by system whenever a device associated with this app is disconnected.
+     *
+     * @param associationInfo A record for the companion device.
+     */
+    @MainThread
+    public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
+        if (!associationInfo.isSelfManaged()) {
+            onDeviceDisappeared(associationInfo.getDeviceMacAddressAsString());
+        }
+    }
+
     @Nullable
     @Override
     public final IBinder onBind(@NonNull Intent intent) {
         if (Objects.equals(intent.getAction(), SERVICE_INTERFACE)) {
+            onBindCompanionDeviceService(intent);
             return mRemote;
         }
         Log.w(LOG_TAG,
@@ -129,20 +177,26 @@
         return null;
     }
 
+    /**
+     * Used to track the state of Binder connection in CTS tests.
+     * @hide
+     */
+    @TestApi
+    public void onBindCompanionDeviceService(@NonNull Intent intent) {
+    }
+
     class Stub extends ICompanionDeviceService.Stub {
 
         @Override
-        public void onDeviceAppeared(String address) {
-            Handler.getMain().sendMessage(PooledLambda.obtainMessage(
-                    CompanionDeviceService::onDeviceAppeared,
-                    CompanionDeviceService.this, address));
+        public void onDeviceAppeared(AssociationInfo associationInfo) {
+            Handler.getMain().post(
+                    () -> CompanionDeviceService.this.onDeviceAppeared(associationInfo));
         }
 
         @Override
-        public void onDeviceDisappeared(String address) {
-            Handler.getMain().sendMessage(PooledLambda.obtainMessage(
-                    CompanionDeviceService::onDeviceDisappeared,
-                    CompanionDeviceService.this, address));
+        public void onDeviceDisappeared(AssociationInfo associationInfo) {
+            Handler.getMain().post(
+                    () -> CompanionDeviceService.this.onDeviceDisappeared(associationInfo));
         }
 
         public void onDispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 1558db2..68a6031f 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -65,5 +65,10 @@
     void dispatchMessage(in int messageId, in int associationId, in byte[] message);
 
     void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+
     void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+
+    void notifyDeviceAppeared(int associationId);
+
+    void notifyDeviceDisappeared(int associationId);
 }
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 25212a1..4e453573 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -16,9 +16,11 @@
 
 package android.companion;
 
+import android.companion.AssociationInfo;
+
 /** @hide */
 oneway interface ICompanionDeviceService {
-    void onDeviceAppeared(in String address);
-    void onDeviceDisappeared(in String address);
+    void onDeviceAppeared(in AssociationInfo associationInfo);
+    void onDeviceDisappeared(in AssociationInfo associationInfo);
     void onDispatchMessage(in int messageId, in int associationId, in byte[] message);
 }
diff --git a/core/java/android/companion/OWNERS b/core/java/android/companion/OWNERS
index 54b35fc..004f66c 100644
--- a/core/java/android/companion/OWNERS
+++ b/core/java/android/companion/OWNERS
@@ -1,4 +1,5 @@
 ewol@google.com
 evanxinchen@google.com
 guojing@google.com
-svetoslavganov@google.com
\ No newline at end of file
+svetoslavganov@google.com
+sergeynv@google.com
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 0aa442b..82ad150 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -16,11 +16,54 @@
 
 package android.companion.virtual;
 
+import android.graphics.Point;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+
 /**
  * Interface for a virtual device.
  *
  * @hide
  */
 interface IVirtualDevice {
+
+    /**
+     * Returns the association ID for this virtual device.
+     *
+     * @see AssociationInfo#getId()
+     */
+    int getAssociationId();
+
+    /**
+     * Closes the virtual device and frees all associated resources.
+     */
     void close();
+    void createVirtualKeyboard(
+            int displayId,
+            String inputDeviceName,
+            int vendorId,
+            int productId,
+            IBinder token);
+    void createVirtualMouse(
+            int displayId,
+            String inputDeviceName,
+            int vendorId,
+            int productId,
+            IBinder token);
+    void createVirtualTouchscreen(
+            int displayId,
+            String inputDeviceName,
+            int vendorId,
+            int productId,
+            IBinder token,
+            in Point screenSize);
+    void unregisterInputDevice(IBinder token);
+    boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+    boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+    boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+    boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+    boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 }
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 91e717d..2dfa202 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -25,5 +25,14 @@
  */
 interface IVirtualDeviceManager {
 
-    IVirtualDevice createVirtualDevice();
+    /**
+     * Creates a virtual device that can be used to create virtual displays and stream contents.
+     *
+     * @param token The binder token created by the caller of this API.
+     * @param packageName The package name of the caller. Implementation of this method must verify
+     *   that this belongs to the calling UID.
+     * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
+     *   CDM. Virtual devices must have a corresponding association with CDM in order to be created.
+     */
+    IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6187de5..0d024b1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -21,7 +21,15 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.companion.AssociationInfo;
 import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualTouchscreen;
+import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 
 /**
@@ -49,14 +57,18 @@
     /**
      * Creates a virtual device.
      *
+     * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from
+     *   Companion Device Manager. Virtual devices must have a corresponding association with CDM in
+     *   order to be created.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Nullable
-    public VirtualDevice createVirtualDevice() {
-        // TODO(b/194949534): Add CDM association ID here and unhide this API
+    public VirtualDevice createVirtualDevice(int associationId) {
+        // TODO(b/194949534): Unhide this API
         try {
-            IVirtualDevice virtualDevice = mService.createVirtualDevice();
+            IVirtualDevice virtualDevice = mService.createVirtualDevice(
+                    new Binder(), mContext.getPackageName(), associationId);
             return new VirtualDevice(mContext, virtualDevice);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -67,6 +79,8 @@
      * 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.
+     *
+     * TODO(b/204081582): Consider using a builder pattern for the input APIs.
      */
     public static class VirtualDevice implements AutoCloseable {
 
@@ -89,5 +103,88 @@
                 throw e.rethrowFromSystemServer();
             }
         }
+
+        /**
+         * 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 vendor id
+         * @param productId the product id
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualKeyboard createVirtualKeyboard(
+                @NonNull VirtualDisplay display,
+                @NonNull String inputDeviceName,
+                int vendorId,
+                int productId) {
+            try {
+                final IBinder token = new Binder(
+                        "android.hardware.input.VirtualKeyboard:" + inputDeviceName);
+                mVirtualDevice.createVirtualKeyboard(display.getDisplay().getDisplayId(),
+                        inputDeviceName, vendorId, productId, token);
+                return new VirtualKeyboard(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 inputDeviceName the name to call this input device
+         * @param vendorId the vendor id
+         * @param productId the product id
+         * @hide
+         */
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        @NonNull
+        public VirtualMouse createVirtualMouse(
+                @NonNull VirtualDisplay display,
+                @NonNull String inputDeviceName,
+                int vendorId,
+                int productId) {
+            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);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Creates a virtual touchscreen.
+         *
+         * @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 vendor id
+         * @param productId the product id
+         * @hide
+         */
+        @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();
+            }
+        }
     }
 }
diff --git a/core/java/android/content/AttributionSource.aidl b/core/java/android/content/AttributionSource.aidl
index 10d5c27..7554cb2 100644
--- a/core/java/android/content/AttributionSource.aidl
+++ b/core/java/android/content/AttributionSource.aidl
@@ -16,4 +16,5 @@
 
 package android.content;
 
+@JavaOnlyStableParcelable
 parcelable AttributionSource;
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 6ae2bb5..157e709 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
+import android.app.AppGlobals;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
@@ -191,10 +192,42 @@
         return new ScopedParcelState(this);
     }
 
-    /** @hide */
-    public static AttributionSource myAttributionSource() {
-        return new AttributionSource(Process.myUid(), ActivityThread.currentOpPackageName(),
-                /*attributionTag*/ null, (String[]) /*renouncedPermissions*/ null, /*next*/ null);
+    /**
+     * Returns a generic {@link AttributionSource} that represents the entire
+     * calling process.
+     *
+     * <p>Callers are <em>strongly</em> encouraged to use a more specific
+     * attribution source whenever possible, such as from
+     * {@link Context#getAttributionSource()}, since that enables developers to
+     * have more detailed and scoped control over attribution within
+     * sub-components of their app.
+     *
+     * @see Context#createAttributionContext(String)
+     * @see Context#getAttributionTag()
+     * @return a generic {@link AttributionSource} representing the entire
+     *         calling process
+     * @throws IllegalStateException when no accurate {@link AttributionSource}
+     *         can be determined
+     */
+    public static @NonNull AttributionSource myAttributionSource() {
+
+        final AttributionSource globalSource = ActivityThread.currentAttributionSource();
+        if (globalSource != null) {
+            return globalSource;
+        }
+
+        int uid = Process.myUid();
+        if (uid == Process.ROOT_UID) {
+            uid = Process.SYSTEM_UID;
+        }
+        try {
+            return new AttributionSource.Builder(uid)
+                .setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
+                .build();
+        } catch (Exception ignored) {
+        }
+
+        throw new IllegalStateException("Failed to resolve AttributionSource");
     }
 
     /**
@@ -247,7 +280,7 @@
      * whether the attribution source is one for the calling app to prevent the caller
      * to pass you a source from another app without including themselves in the
      * attribution chain.
-     *f
+     *
      * @return if the attribution source cannot be trusted to be from the caller.
      */
     public boolean checkCallingUid() {
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 1d4d30d..d46a0c6 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -27,6 +27,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -98,6 +99,7 @@
         boolean mAbortBroadcast;
         @UnsupportedAppUsage
         boolean mFinished;
+        String mReceiverClassName;
 
         /** @hide */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -219,6 +221,12 @@
          * next broadcast will proceed.
          */
         public final void finish() {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "PendingResult#finish#ClassName:" + mReceiverClassName,
+                        1);
+            }
+
             if (mType == TYPE_COMPONENT) {
                 final IActivityManager mgr = ActivityManager.getService();
                 if (QueuedWork.hasPendingWork()) {
@@ -383,6 +391,14 @@
     public final PendingResult goAsync() {
         PendingResult res = mPendingResult;
         mPendingResult = null;
+
+        if (res != null && Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            res.mReceiverClassName = getClass().getName();
+            Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "BroadcastReceiver#goAsync#ClassName:" + res.mReceiverClassName,
+                    1);
+        }
+
         return res;
     }
 
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 30775b1..0c065d9b 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -108,7 +108,7 @@
             mExtras = null;
         }
         mSelection = source.readInt() != 0 ? source.readString8() : null;
-        mSelectionArgs = source.readSparseArray(null);
+        mSelectionArgs = source.readSparseArray(null, java.lang.Object.class);
         mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
         mYieldAllowed = source.readInt() != 0;
         mExceptionAllowed = source.readInt() != 0;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index a9a2325..184acb1 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -2666,7 +2666,7 @@
 
     /**
      * Same as {@link #registerContentObserver(Uri, boolean, ContentObserver)}, but the observer
-     * registered will get content change notifications from all users.
+     * registered will get content change notifications for the specified user.
      * {@link ContentObserver#onChange(boolean, Collection, int, UserHandle)} should be
      * overwritten to get the corresponding {@link UserHandle} for that notification.
      *
@@ -2679,21 +2679,26 @@
      *                             whenever a change occurs to the URI's descendants in the path
      *                             hierarchy.
      * @param observer             The object that receives callbacks when changes occur.
+     * @param userHandle           The UserHandle of the user the content change notifications are
+     *                             for.
      * @hide
      * @see #unregisterContentObserver
      */
-    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-    @SystemApi
-    public final void registerContentObserverForAllUsers(@NonNull Uri uri,
+    @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            conditional = true)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public final void registerContentObserverAsUser(@NonNull Uri uri,
             boolean notifyForDescendants,
-            @NonNull ContentObserver observer) {
+            @NonNull ContentObserver observer,
+            @NonNull UserHandle userHandle) {
         Objects.requireNonNull(uri, "uri");
         Objects.requireNonNull(observer, "observer");
+        Objects.requireNonNull(userHandle, "userHandle");
         registerContentObserver(
                 ContentProvider.getUriWithoutUserId(uri),
                 notifyForDescendants,
                 observer,
-                UserHandle.USER_ALL);
+                userHandle.getIdentifier());
     }
 
     /** @hide - designated user version */
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2f2151e..6d13712 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4790,6 +4790,15 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.view.selectiontoolbar.SelectionToolbarManager} for selection toolbar service.
+     *
+     * @see #getSystemService(String)
+     * @hide
+     */
+    public static final String SELECTION_TOOLBAR_SERVICE = "selection_toolbar";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.graphics.fonts.FontManager} for font services.
      *
      * @see #getSystemService(String)
@@ -5356,8 +5365,9 @@
      * {@link android.net.NetworkScoreManager} for managing network scoring.
      * @see #getSystemService(String)
      * @see android.net.NetworkScoreManager
-     * @deprecated see https://developer.android.com/guide/topics/connectivity/wifi-suggest for
-     * alternative API to propose WiFi networks.
+     * @deprecated see
+     * <a href="{@docRoot}guide/topics/connectivity/wifi-suggest">Wi-Fi Suggestion API</a>
+     * for alternative API to propose WiFi networks.
      * @hide
      */
     @SystemApi
@@ -5861,13 +5871,14 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
-     * {@link android.app.CommunalManager} for interacting with the global system state.
+     * {@link android.app.communal.CommunalManager} for interacting with the global system state.
      *
      * @see #getSystemService(String)
-     * @see android.app.CommunalManager
+     * @see android.app.communal.CommunalManager
      * @hide
      */
-    public static final String COMMUNAL_MANAGER_SERVICE = "communal_manager";
+    @SystemApi
+    public static final String COMMUNAL_SERVICE = "communal";
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a36d532..1e6029f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -18,6 +18,7 @@
 
 import static android.content.ContentProvider.maybeAddUserId;
 
+import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.annotation.AnyRes;
 import android.annotation.BroadcastBehavior;
@@ -2041,6 +2042,21 @@
             "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
 
     /**
+     * Activity action: Launch the Safety Hub UI.
+     *
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    public static final String ACTION_VIEW_SAFETY_HUB =
+            "android.intent.action.VIEW_SAFETY_HUB";
+
+    /**
      * Activity action: Launch UI to manage a default app.
      * <p>
      * Input: {@link #EXTRA_ROLE_NAME} specifies the role of the default app which will be managed
@@ -5899,6 +5915,14 @@
     public static final String EXTRA_UID = "android.intent.extra.UID";
 
     /**
+     * Used as an optional int extra field in {@link android.content.Intent#ACTION_PACKAGE_ADDED}
+     * intents to supply the previous uid the package had been assigned.
+     * This would only be set when a package is leaving sharedUserId in an upgrade, or when a
+     * system app upgrade that had left sharedUserId is getting uninstalled.
+     */
+    public static final String EXTRA_PREVIOUS_UID = "android.intent.extra.PREVIOUS_UID";
+
+    /**
      * @hide String array of package names.
      */
     @SystemApi
@@ -5930,6 +5954,16 @@
     public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
 
     /**
+     * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED},
+     * {@link android.content.Intent#ACTION_UID_REMOVED}, and
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED}
+     * intents to indicate that this package is changing its UID.
+     * This would only be set when a package is leaving sharedUserId in an upgrade, or when a
+     * system app upgrade that had left sharedUserId is getting uninstalled.
+     */
+    public static final String EXTRA_UID_CHANGING = "android.intent.extra.UID_CHANGING";
+
+    /**
      * 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.
@@ -9220,7 +9254,7 @@
      * @see #resolveActivity
      */
     public ActivityInfo resolveActivityInfo(@NonNull PackageManager pm,
-            @PackageManager.ComponentInfoFlags int flags) {
+            @PackageManager.ComponentInfoFlagsBits int flags) {
         ActivityInfo ai = null;
         if (mComponent != null) {
             try {
@@ -9248,7 +9282,7 @@
      */
     @UnsupportedAppUsage
     public @Nullable ComponentName resolveSystemService(@NonNull PackageManager pm,
-            @PackageManager.ComponentInfoFlags int flags) {
+            @PackageManager.ComponentInfoFlagsBits int flags) {
         if (mComponent != null) {
             return mComponent;
         }
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 432e81b..6830f5f 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -84,7 +84,7 @@
     }
 
     private PeriodicSync(Parcel in) {
-        this.account = in.readParcelable(null);
+        this.account = in.readParcelable(null, android.accounts.Account.class);
         this.authority = in.readString();
         this.extras = in.readBundle();
         this.period = in.readLong();
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index 017a92b..57101be 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -99,7 +99,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     SyncInfo(Parcel parcel) {
         authorityId = parcel.readInt();
-        account = parcel.readParcelable(Account.class.getClassLoader());
+        account = parcel.readParcelable(Account.class.getClassLoader(), android.accounts.Account.class);
         authority = parcel.readString();
         startTime = parcel.readLong();
     }
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index e1e6f75..83ce84e 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -174,7 +174,7 @@
         mIsAuthority = (in.readInt() != 0);
         mIsExpedited = (in.readInt() != 0);
         mIsScheduledAsExpeditedJob = (in.readInt() != 0);
-        mAccountToSync = in.readParcelable(null);
+        mAccountToSync = in.readParcelable(null, android.accounts.Account.class);
         mAuthority = in.readString();
     }
 
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java
index 87afbf8..b2979f3 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -777,7 +777,7 @@
             final int N = p.readInt();
             for (int i=0; i<N; i++) {
                 UndoOwner owner = mManager.restoreOwner(p);
-                UndoOperation op = (UndoOperation)p.readParcelable(loader);
+                UndoOperation op = (UndoOperation)p.readParcelable(loader, android.content.UndoOperation.class);
                 op.mOwner = owner;
                 mOperations.add(op);
             }
diff --git a/core/java/android/content/UriPermission.java b/core/java/android/content/UriPermission.java
index d3a9cb8..7347761 100644
--- a/core/java/android/content/UriPermission.java
+++ b/core/java/android/content/UriPermission.java
@@ -47,7 +47,7 @@
 
     /** {@hide} */
     public UriPermission(Parcel in) {
-        mUri = in.readParcelable(null);
+        mUri = in.readParcelable(null, android.net.Uri.class);
         mModeFlags = in.readInt();
         mPersistedTime = in.readLong();
     }
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 73be0ff..868dab2 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -67,7 +67,7 @@
         mRequests = new ArrayList<>(size);
         for (int i = 0; i < size; i++) {
             final int request = source.readInt();
-            final OverlayIdentifier overlay = source.readParcelable(null);
+            final OverlayIdentifier overlay = source.readParcelable(null, android.content.om.OverlayIdentifier.class);
             final int userId = source.readInt();
             final Bundle extras = source.readBundle(null);
             mRequests.add(new Request(request, overlay, userId, extras));
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 58f20ef..8bea006 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -304,12 +304,23 @@
      * @see android.R.attr#colorMode
      */
     public static final int COLOR_MODE_HDR = 2;
+    // 3 Corresponds to android::uirenderer::ColorMode::Hdr10.
+    /**
+     * Value of {@link #colorMode} indicating that the activity should use an
+     * 8 bit alpha buffer if the presentation display supports it.
+     *
+     * @see android.R.attr#colorMode
+     * @hide
+     */
+    public static final int COLOR_MODE_A8 = 4;
+
 
     /** @hide */
     @IntDef(prefix = { "COLOR_MODE_" }, value = {
             COLOR_MODE_DEFAULT,
             COLOR_MODE_WIDE_COLOR_GAMUT,
             COLOR_MODE_HDR,
+            COLOR_MODE_A8,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ColorMode {}
@@ -1017,12 +1028,12 @@
      * This change id restricts treatments that force a given min aspect ratio to activities
      * whose orientation is fixed to portrait.
      *
-     * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+     * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is
+     * also enabled.
      * @hide
      */
     @ChangeId
     @Overridable
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2)
     @TestApi
     public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id
 
@@ -1374,18 +1385,18 @@
      * Returns if the activity should never be sandboxed to the activity window bounds.
      * @hide
      */
-    public boolean neverSandboxDisplayApis() {
+    public boolean neverSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) {
         return isChangeEnabled(NEVER_SANDBOX_DISPLAY_APIS)
-                || ConstrainDisplayApisConfig.neverConstrainDisplayApis(applicationInfo);
+                || constrainDisplayApisConfig.getNeverConstrainDisplayApis(applicationInfo);
     }
 
     /**
      * Returns if the activity should always be sandboxed to the activity window bounds.
      * @hide
      */
-    public boolean alwaysSandboxDisplayApis() {
+    public boolean alwaysSandboxDisplayApis(ConstrainDisplayApisConfig constrainDisplayApisConfig) {
         return isChangeEnabled(ALWAYS_SANDBOX_DISPLAY_APIS)
-                || ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(applicationInfo);
+                || constrainDisplayApisConfig.getAlwaysConstrainDisplayApis(applicationInfo);
     }
 
     /** @hide */
@@ -1682,6 +1693,8 @@
                 return "COLOR_MODE_WIDE_COLOR_GAMUT";
             case COLOR_MODE_HDR:
                 return "COLOR_MODE_HDR";
+            case COLOR_MODE_A8:
+                return "COLOR_MODE_A8";
             default:
                 return Integer.toString(colorMode);
         }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2c4ff58..76b4e5c 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -33,6 +33,7 @@
 import android.os.Parcelable;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.util.ArrayMap;
 import android.util.Printer;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -409,7 +410,7 @@
 
     /**
      * Value for {@link #flags}: {@code true} if the application may use cleartext network traffic
-     * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP
+     * (e.g., HTTP rather than HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, SMTP
      * without STARTTLS or TLS). If {@code false}, the app declares that it does not intend to use
      * cleartext network traffic, in which case platform components (e.g., HTTP stacks,
      * {@code DownloadManager}, {@code MediaPlayer}) will refuse app's requests to use cleartext
@@ -1148,6 +1149,12 @@
     public int versionCode;
 
     /**
+     * The timestamp of when this ApplicationInfo was created.
+     * @hide
+     */
+    public long createTimestamp;
+
+    /**
      * The user-visible SDK version (ex. 26) of the framework against which the application claims
      * to have been compiled, or {@code 0} if not specified.
      * <p>
@@ -1527,6 +1534,15 @@
 
     private int mHiddenApiPolicy = HIDDEN_API_ENFORCEMENT_DEFAULT;
 
+    /**
+     * A map from a process name to an (custom) application class name in this package, derived
+     * from the <processes> tag in the app's manifest. This map may not contain all the process
+     * names. Processses not in this map will use the default app class name,
+     * which is {@link #className}, or the default class {@link android.app.Application}.
+     */
+    @Nullable
+    private ArrayMap<String, String> mAppClassNamesByProcess;
+
     public void dump(Printer pw, String prefix) {
         dump(pw, prefix, DUMP_FLAG_ALL);
     }
@@ -1534,8 +1550,14 @@
     /** @hide */
     public void dump(Printer pw, String prefix, int dumpFlags) {
         super.dumpFront(pw, prefix);
-        if ((dumpFlags & DUMP_FLAG_DETAILS) != 0 && className != null) {
-            pw.println(prefix + "className=" + className);
+        if ((dumpFlags & DUMP_FLAG_DETAILS) != 0) {
+            if (className != null) {
+                pw.println(prefix + "className=" + className);
+            }
+            for (int i = 0; i < ArrayUtils.size(mAppClassNamesByProcess); i++) {
+                pw.println(prefix + "  process=" + mAppClassNamesByProcess.keyAt(i)
+                        + " className=" + mAppClassNamesByProcess.valueAt(i));
+            }
         }
         if (permission != null) {
             pw.println(prefix + "permission=" + permission);
@@ -1639,6 +1661,7 @@
                         + requestRawExternalStorageAccess);
             }
         }
+        pw.println(prefix + "createTimestamp=" + createTimestamp);
         super.dumpBack(pw, prefix);
     }
 
@@ -1796,6 +1819,7 @@
     }
 
     public ApplicationInfo() {
+        createTimestamp = System.currentTimeMillis();
     }
     
     public ApplicationInfo(ApplicationInfo orig) {
@@ -1867,6 +1891,7 @@
         memtagMode = orig.memtagMode;
         nativeHeapZeroInitialized = orig.nativeHeapZeroInitialized;
         requestRawExternalStorageAccess = orig.requestRawExternalStorageAccess;
+        createTimestamp = System.currentTimeMillis();
     }
 
     public String toString() {
@@ -1957,6 +1982,17 @@
         dest.writeInt(memtagMode);
         dest.writeInt(nativeHeapZeroInitialized);
         sForBoolean.parcel(requestRawExternalStorageAccess, dest, parcelableFlags);
+        dest.writeLong(createTimestamp);
+        if (mAppClassNamesByProcess == null) {
+            dest.writeInt(0);
+        } else {
+            final int size = mAppClassNamesByProcess.size();
+            dest.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                dest.writeString(mAppClassNamesByProcess.keyAt(i));
+                dest.writeString(mAppClassNamesByProcess.valueAt(i));
+            }
+        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -2044,6 +2080,14 @@
         memtagMode = source.readInt();
         nativeHeapZeroInitialized = source.readInt();
         requestRawExternalStorageAccess = sForBoolean.unparcel(source);
+        createTimestamp = source.readLong();
+        final int allClassesSize = source.readInt();
+        if (allClassesSize > 0) {
+            mAppClassNamesByProcess = new ArrayMap<>(allClassesSize);
+            for (int i = 0; i < allClassesSize; i++) {
+                mAppClassNamesByProcess.put(source.readString(), source.readString());
+            }
+        }
     }
 
     /**
@@ -2527,6 +2571,19 @@
         requestRawExternalStorageAccess = value;
     }
 
+    /**
+     * Replaces {@link #mAppClassNamesByProcess}. This takes over the ownership of the passed map.
+     * Do not modify the argument at the callsite.
+     * {@hide}
+     */
+    public void setAppClassNamesByProcess(@Nullable ArrayMap<String, String> value) {
+        if (ArrayUtils.size(value) == 0) {
+            mAppClassNamesByProcess = null;
+        } else {
+            mAppClassNamesByProcess = value;
+        }
+    }
+
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public String getCodePath() { return scanSourceDir; }
@@ -2557,4 +2614,21 @@
     public int getNativeHeapZeroInitialized() {
         return nativeHeapZeroInitialized;
     }
+
+    /**
+     * Return the application class name defined in the manifest. The class name set in the
+     * <processes> tag for this process, then return it. Otherwise it'll return the class
+     * name set in the <application> tag. If neither is set, it'll return null.
+     * @hide
+     */
+    @Nullable
+    public String getCustomApplicationClassNameForProcess(String processName) {
+        if (mAppClassNamesByProcess != null) {
+            String byProcess = mAppClassNamesByProcess.get(processName);
+            if (byProcess != null) {
+                return byProcess;
+            }
+        }
+        return className;
+    }
 }
diff --git a/core/java/android/content/pm/ConstrainDisplayApisConfig.java b/core/java/android/content/pm/ConstrainDisplayApisConfig.java
index 11ba3d4..98b73aa 100644
--- a/core/java/android/content/pm/ConstrainDisplayApisConfig.java
+++ b/core/java/android/content/pm/ConstrainDisplayApisConfig.java
@@ -19,10 +19,15 @@
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
 
 import android.provider.DeviceConfig;
+import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 
+import com.android.internal.os.BackgroundThread;
+
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Class for processing flags in the Device Config namespace 'constrain_display_apis'.
@@ -55,19 +60,45 @@
             "always_constrain_display_apis";
 
     /**
+     * Indicates that display APIs should never be constrained to the activity window bounds for all
+     * packages.
+     */
+    private boolean mNeverConstrainDisplayApisAllPackages;
+
+    /**
+     * Indicates that display APIs should never be constrained to the activity window bounds for
+     * a set of defined packages. Map keys are package names, and entries are a
+     * 'Pair(<min-version-code>, <max-version-code>)'.
+     */
+    private ArrayMap<String, Pair<Long, Long>> mNeverConstrainConfigMap;
+
+    /**
+     * Indicates that display APIs should always be constrained to the activity window bounds for
+     * a set of defined packages. Map keys are package names, and entries are a
+     * 'Pair(<min-version-code>, <max-version-code>)'.
+     */
+    private ArrayMap<String, Pair<Long, Long>> mAlwaysConstrainConfigMap;
+
+    public ConstrainDisplayApisConfig() {
+        updateCache();
+
+        DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                BackgroundThread.getExecutor(), properties -> updateCache());
+    }
+
+    /**
      * Returns true if either the flag 'never_constrain_display_apis_all_packages' is true or the
      * flag 'never_constrain_display_apis' contains a package entry that matches the given {@code
      * applicationInfo}.
      *
      * @param applicationInfo Information about the application/package.
      */
-    public static boolean neverConstrainDisplayApis(ApplicationInfo applicationInfo) {
-        if (DeviceConfig.getBoolean(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
-                FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false)) {
+    public boolean getNeverConstrainDisplayApis(ApplicationInfo applicationInfo) {
+        if (mNeverConstrainDisplayApisAllPackages) {
             return true;
         }
 
-        return flagHasMatchingPackageEntry(FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, applicationInfo);
+        return flagHasMatchingPackageEntry(mNeverConstrainConfigMap, applicationInfo);
     }
 
     /**
@@ -76,73 +107,106 @@
      *
      * @param applicationInfo Information about the application/package.
      */
-    public static boolean alwaysConstrainDisplayApis(ApplicationInfo applicationInfo) {
-        return flagHasMatchingPackageEntry(FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, applicationInfo);
+    public boolean getAlwaysConstrainDisplayApis(ApplicationInfo applicationInfo) {
+        return flagHasMatchingPackageEntry(mAlwaysConstrainConfigMap, applicationInfo);
+    }
+
+
+    /**
+     * Updates {@link #mNeverConstrainDisplayApisAllPackages}, {@link #mNeverConstrainConfigMap},
+     * and {@link #mAlwaysConstrainConfigMap} from the {@link DeviceConfig}.
+     */
+    private void updateCache() {
+        mNeverConstrainDisplayApisAllPackages = DeviceConfig.getBoolean(
+                NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                FLAG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, /* defaultValue= */ false);
+
+        final String neverConstrainConfigStr = DeviceConfig.getString(
+                NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                FLAG_NEVER_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ "");
+        mNeverConstrainConfigMap = buildConfigMap(neverConstrainConfigStr);
+
+        final String alwaysConstrainConfigStr = DeviceConfig.getString(
+                NAMESPACE_CONSTRAIN_DISPLAY_APIS,
+                FLAG_ALWAYS_CONSTRAIN_DISPLAY_APIS, /* defaultValue= */ "");
+        mAlwaysConstrainConfigMap = buildConfigMap(alwaysConstrainConfigStr);
+    }
+
+    /**
+     * Processes the configuration string into a map of version codes, for the given
+     * configuration to be applied to the specified packages. If the given package
+     * entry string is invalid, then the map will not contain an entry for the package.
+     *
+     * @param configStr A configuration string expected to be in the format of a list of package
+     *                  entries separated by ','. A package entry expected to be in the format
+     *                  '<package-name>:<min-version-code>?:<max-version-code>?'.
+     * @return a map of configuration entries, where each key is a package name. Each value is
+     * a pair of version codes, in the format 'Pair(<min-version-code>, <max-version-code>)'.
+     */
+    private static ArrayMap<String, Pair<Long, Long>> buildConfigMap(String configStr) {
+        ArrayMap<String, Pair<Long, Long>> configMap = new ArrayMap<>();
+        // String#split returns a non-empty array given an empty string.
+        if (configStr.isEmpty()) {
+            return configMap;
+        }
+        for (String packageEntryString : configStr.split(",")) {
+            List<String> packageAndVersions = Arrays.asList(packageEntryString.split(":", 3));
+            if (packageAndVersions.size() != 3) {
+                Slog.w(TAG, "Invalid package entry in flag 'never/always_constrain_display_apis': "
+                        + packageEntryString);
+                // Skip this entry.
+                continue;
+            }
+            String packageName = packageAndVersions.get(0);
+            String minVersionCodeStr = packageAndVersions.get(1);
+            String maxVersionCodeStr = packageAndVersions.get(2);
+            try {
+                final long minVersion =
+                        minVersionCodeStr.isEmpty() ? Long.MIN_VALUE : Long.parseLong(
+                                minVersionCodeStr);
+                final long maxVersion =
+                        maxVersionCodeStr.isEmpty() ? Long.MAX_VALUE : Long.parseLong(
+                                maxVersionCodeStr);
+                Pair<Long, Long> minMaxVersionCodes = new Pair<>(minVersion, maxVersion);
+                configMap.put(packageName, minMaxVersionCodes);
+            } catch (NumberFormatException e) {
+                Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryString);
+                // Skip this entry.
+            }
+        }
+        return configMap;
     }
 
     /**
      * Returns true if the flag with the given {@code flagName} contains a package entry that
      * matches the given {@code applicationInfo}.
      *
+     * @param configMap the map representing the current configuration value to examine
      * @param applicationInfo Information about the application/package.
      */
-    private static boolean flagHasMatchingPackageEntry(String flagName,
+    private static boolean flagHasMatchingPackageEntry(Map<String, Pair<Long, Long>> configMap,
             ApplicationInfo applicationInfo) {
-        String configStr = DeviceConfig.getString(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
-                flagName, /* defaultValue= */ "");
-
-        // String#split returns a non-empty array given an empty string.
-        if (configStr.isEmpty()) {
+        if (configMap.isEmpty()) {
             return false;
         }
-
-        for (String packageEntryString : configStr.split(",")) {
-            if (matchesApplicationInfo(packageEntryString, applicationInfo)) {
-                return true;
-            }
+        if (!configMap.containsKey(applicationInfo.packageName)) {
+            return false;
         }
-
-        return false;
+        return matchesApplicationInfo(configMap.get(applicationInfo.packageName), applicationInfo);
     }
 
     /**
-     * Parses the given {@code packageEntryString} and returns true if {@code
-     * applicationInfo.packageName} matches the package name in the config and {@code
-     * applicationInfo.longVersionCode} is within the version range in the config.
+     * Parses the given {@code minMaxVersionCodes} and returns true if {@code
+     * applicationInfo.longVersionCode} is within the version range in the pair.
+     * Returns false otherwise.
      *
-     * <p>Logs a warning and returns false in case the given {@code packageEntryString} is invalid.
-     *
-     * @param packageEntryStr A package entry expected to be in the format
-     *                        '<package-name>:<min-version-code>?:<max-version-code>?'.
+     * @param minMaxVersionCodes A pair expected to be in the format
+     *                        'Pair(<min-version-code>, <max-version-code>)'.
      * @param applicationInfo Information about the application/package.
      */
-    private static boolean matchesApplicationInfo(String packageEntryStr,
+    private static boolean matchesApplicationInfo(Pair<Long, Long> minMaxVersionCodes,
             ApplicationInfo applicationInfo) {
-        List<String> packageAndVersions = Arrays.asList(packageEntryStr.split(":", 3));
-        if (packageAndVersions.size() != 3) {
-            Slog.w(TAG, "Invalid package entry in flag 'never_constrain_display_apis': "
-                    + packageEntryStr);
-            return false;
-        }
-        String packageName = packageAndVersions.get(0);
-        String minVersionCodeStr = packageAndVersions.get(1);
-        String maxVersionCodeStr = packageAndVersions.get(2);
-
-        if (!packageName.equals(applicationInfo.packageName)) {
-            return false;
-        }
-        long version = applicationInfo.longVersionCode;
-        try {
-            if (!minVersionCodeStr.isEmpty() && version < Long.parseLong(minVersionCodeStr)) {
-                return false;
-            }
-            if (!maxVersionCodeStr.isEmpty() && version > Long.parseLong(maxVersionCodeStr)) {
-                return false;
-            }
-        } catch (NumberFormatException e) {
-            Slog.w(TAG, "Invalid APK version code in package entry: " + packageEntryStr);
-            return false;
-        }
-        return true;
+        return applicationInfo.longVersionCode >= minMaxVersionCodes.first
+                && applicationInfo.longVersionCode <= minMaxVersionCodes.second;
     }
 }
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index a45bf79..84d2ca3 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -61,7 +61,7 @@
 
     private InstallSourceInfo(Parcel source) {
         mInitiatingPackageName = source.readString();
-        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
+        mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader(), android.content.pm.SigningInfo.class);
         mOriginatingPackageName = source.readString();
         mInstallingPackageName = source.readString();
     }
diff --git a/core/java/android/content/pm/InstantAppInfo.java b/core/java/android/content/pm/InstantAppInfo.java
index 24d6a07..d6cfb0e 100644
--- a/core/java/android/content/pm/InstantAppInfo.java
+++ b/core/java/android/content/pm/InstantAppInfo.java
@@ -65,7 +65,7 @@
         mLabelText = parcel.readCharSequence();
         mRequestedPermissions = parcel.readStringArray();
         mGrantedPermissions = parcel.createStringArray();
-        mApplicationInfo = parcel.readParcelable(null);
+        mApplicationInfo = parcel.readParcelable(null, android.content.pm.ApplicationInfo.class);
     }
 
     /**
diff --git a/core/java/android/content/pm/InstantAppIntentFilter.java b/core/java/android/content/pm/InstantAppIntentFilter.java
index 123d2ba..721b261 100644
--- a/core/java/android/content/pm/InstantAppIntentFilter.java
+++ b/core/java/android/content/pm/InstantAppIntentFilter.java
@@ -46,7 +46,7 @@
 
     InstantAppIntentFilter(Parcel in) {
         mSplitName = in.readString();
-        in.readList(mFilters, getClass().getClassLoader());
+        in.readList(mFilters, getClass().getClassLoader(), android.content.IntentFilter.class);
     }
 
     public String getSplitName() {
diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java
index 9881564..6124638 100644
--- a/core/java/android/content/pm/InstantAppResolveInfo.java
+++ b/core/java/android/content/pm/InstantAppResolveInfo.java
@@ -140,7 +140,7 @@
             mFilters = Collections.emptyList();
             mVersionCode = -1;
         } else {
-            mDigest = in.readParcelable(null /*loader*/);
+            mDigest = in.readParcelable(null /*loader*/, android.content.pm.InstantAppResolveInfo.InstantAppDigest.class);
             mPackageName = in.readString();
             mFilters = new ArrayList<>();
             in.readTypedList(mFilters, InstantAppIntentFilter.CREATOR);
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 417f168..46c415d 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -43,10 +43,10 @@
     }
 
     public LauncherActivityInfoInternal(Parcel source) {
-        mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader());
+        mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class);
         mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
         mIncrementalStatesInfo = source.readParcelable(
-                IncrementalStatesInfo.class.getClassLoader());
+                IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class);
     }
 
     public ComponentName getComponentName() {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index ae5f71bc..617d3ab 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -41,7 +41,7 @@
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionCallbackDelegate;
 import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageManager.ApplicationInfoFlags;
+import android.content.pm.PackageManager.ApplicationInfoFlagsBits;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -1012,7 +1012,7 @@
      *         isn't enabled.
      */
     public ApplicationInfo getApplicationInfo(@NonNull String packageName,
-            @ApplicationInfoFlags int flags, @NonNull UserHandle user)
+            @ApplicationInfoFlagsBits int flags, @NonNull UserHandle user)
             throws PackageManager.NameNotFoundException {
         Objects.requireNonNull(packageName, "packageName");
         Objects.requireNonNull(user, "user");
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 80584d1..251d5e8 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -28,6 +28,7 @@
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -1354,6 +1355,7 @@
          *                          {@link PackageManager#TRUST_NONE} disables optimized
          *                          installer-enforced checksums, otherwise the list has to be
          *                          a non-empty list of certificates.
+         * @param executor the {@link Executor} on which to invoke the callback
          * @param onChecksumsReadyListener called once when the results are available.
          * @throws CertificateEncodingException if an encoding error occurs for trustedInstallers.
          * @throws FileNotFoundException if the file does not exist.
@@ -1361,11 +1363,13 @@
          */
         public void requestChecksums(@NonNull String name, @Checksum.TypeMask int required,
                 @NonNull List<Certificate> trustedInstallers,
+                @NonNull @CallbackExecutor Executor executor,
                 @NonNull PackageManager.OnChecksumsReadyListener onChecksumsReadyListener)
                 throws CertificateEncodingException, FileNotFoundException {
             Objects.requireNonNull(name);
-            Objects.requireNonNull(onChecksumsReadyListener);
             Objects.requireNonNull(trustedInstallers);
+            Objects.requireNonNull(executor);
+            Objects.requireNonNull(onChecksumsReadyListener);
             if (trustedInstallers == PackageManager.TRUST_ALL) {
                 trustedInstallers = null;
             } else if (trustedInstallers == PackageManager.TRUST_NONE) {
@@ -1381,7 +1385,8 @@
                             @Override
                             public void onChecksumsReady(List<ApkChecksum> checksums)
                                     throws RemoteException {
-                                onChecksumsReadyListener.onChecksumsReady(checksums);
+                                executor.execute(
+                                        () -> onChecksumsReadyListener.onChecksumsReady(checksums));
                             }
                         };
                 mSession.requestChecksums(name, DEFAULT_CHECKSUMS, required,
@@ -1749,11 +1754,11 @@
             installScenario = source.readInt();
             sizeBytes = source.readLong();
             appPackageName = source.readString();
-            appIcon = source.readParcelable(null);
+            appIcon = source.readParcelable(null, android.graphics.Bitmap.class);
             appLabel = source.readString();
-            originatingUri = source.readParcelable(null);
+            originatingUri = source.readParcelable(null, android.net.Uri.class);
             originatingUid = source.readInt();
-            referrerUri = source.readParcelable(null);
+            referrerUri = source.readParcelable(null, android.net.Uri.class);
             abiOverride = source.readString();
             volumeUuid = source.readString();
             grantedRuntimePermissions = source.readStringArray();
@@ -1765,7 +1770,7 @@
             forceQueryableOverride = source.readBoolean();
             requiredInstalledVersionCode = source.readLong();
             DataLoaderParamsParcel dataLoaderParamsParcel = source.readParcelable(
-                    DataLoaderParamsParcel.class.getClassLoader());
+                    DataLoaderParamsParcel.class.getClassLoader(), android.content.pm.DataLoaderParamsParcel.class);
             if (dataLoaderParamsParcel != null) {
                 dataLoaderParams = new DataLoaderParams(dataLoaderParamsParcel);
             }
@@ -2528,13 +2533,13 @@
             installScenario = source.readInt();
             sizeBytes = source.readLong();
             appPackageName = source.readString();
-            appIcon = source.readParcelable(null);
+            appIcon = source.readParcelable(null, android.graphics.Bitmap.class);
             appLabel = source.readString();
 
             installLocation = source.readInt();
-            originatingUri = source.readParcelable(null);
+            originatingUri = source.readParcelable(null, android.net.Uri.class);
             originatingUid = source.readInt();
-            referrerUri = source.readParcelable(null);
+            referrerUri = source.readParcelable(null, android.net.Uri.class);
             grantedRuntimePermissions = source.readStringArray();
             whitelistedRestrictedPermissions = source.createStringArrayList();
             autoRevokePermissionsMode = source.readInt();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a7f3801..f984f43 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -22,6 +22,7 @@
 import android.annotation.DrawableRes;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -655,7 +656,7 @@
      */
 
     /** @hide */
-    @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+    @LongDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
             GET_ACTIVITIES,
             GET_CONFIGURATIONS,
             GET_GIDS,
@@ -685,10 +686,10 @@
             GET_ATTRIBUTIONS,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface PackageInfoFlags {}
+    public @interface PackageInfoFlagsBits {}
 
     /** @hide */
-    @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+    @LongDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
             GET_META_DATA,
             GET_SHARED_LIBRARY_FILES,
             MATCH_UNINSTALLED_PACKAGES,
@@ -704,10 +705,10 @@
             MATCH_APEX,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ApplicationInfoFlags {}
+    public @interface ApplicationInfoFlagsBits {}
 
     /** @hide */
-    @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+    @LongDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
             GET_META_DATA,
             GET_SHARED_LIBRARY_FILES,
             MATCH_ALL,
@@ -727,10 +728,10 @@
             GET_UNINSTALLED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ComponentInfoFlags {}
+    public @interface ComponentInfoFlagsBits {}
 
     /** @hide */
-    @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
+    @LongDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
             GET_META_DATA,
             GET_RESOLVED_FILTER,
             GET_SHARED_LIBRARY_FILES,
@@ -750,7 +751,7 @@
             GET_UNINSTALLED_PACKAGES,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface ResolveInfoFlags {}
+    public @interface ResolveInfoFlagsBits {}
 
     /** @hide */
     @IntDef(flag = true, prefix = { "GET_", "MATCH_" }, value = {
@@ -2762,6 +2763,16 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports FeliCa communication, which is based on
+     * ISO/IEC 18092 and JIS X 6319-4.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_FELICA = "android.hardware.felica";
+
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device's
      * {@link ActivityManager#isLowRamDevice() ActivityManager.isLowRamDevice()} method returns
      * true.
@@ -3414,6 +3425,18 @@
      * @hide
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports
+     * {@link android.service.games.GameService}.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    @SystemApi
+    public static final String FEATURE_GAME_SERVICE = "android.software.game_service";
+
+    /**
+     * @hide
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports
      * {@link android.service.voice.VoiceInteractionService} and
      * {@link android.app.VoiceInteractor}.
      */
@@ -3639,6 +3662,14 @@
     public static final String FEATURE_MANAGED_PROFILES = "android.software.managed_users";
 
     /**
+     * @hide
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device supports Nearby mainline feature.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_NEARBY = "android.software.nearby";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
      * The device supports verified boot.
      */
@@ -3951,6 +3982,7 @@
      * @hide
      */
     @SdkConstant(SdkConstantType.FEATURE)
+    @TestApi
     public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
 
     /** @hide */
@@ -4630,6 +4662,74 @@
      */
     public static final String PROPERTY_ALLOW_ADB_BACKUP = "android.backup.ALLOW_ADB_BACKUP";
 
+    /**
+     * Flags class that wraps around the bitmask flags used in methods that retrieve package or
+     * application info.
+     * @hide
+     */
+    public static class Flags {
+        final long mValue;
+        protected Flags(long value) {
+            mValue = value;
+        }
+        public long getValue() {
+            return mValue;
+        }
+    }
+
+    /**
+     * Specific flags used for retrieving package info. Example:
+     * {@code PackageManager.getPackageInfo(packageName, PackageInfoFlags.of(0)}
+     */
+    public final static class PackageInfoFlags extends Flags {
+        private PackageInfoFlags(@PackageInfoFlagsBits long value) {
+            super(value);
+        }
+        @NonNull
+        public static PackageInfoFlags of(@PackageInfoFlagsBits long value) {
+            return new PackageInfoFlags(value);
+        }
+    }
+
+    /**
+     * Specific flags used for retrieving application info.
+     */
+    public final static class ApplicationInfoFlags extends Flags {
+        private ApplicationInfoFlags(@ApplicationInfoFlagsBits long value) {
+            super(value);
+        }
+        @NonNull
+        public static ApplicationInfoFlags of(@ApplicationInfoFlagsBits long value) {
+            return new ApplicationInfoFlags(value);
+        }
+    }
+
+    /**
+     * Specific flags used for retrieving component info.
+     */
+    public final static class ComponentInfoFlags extends Flags {
+        private ComponentInfoFlags(@ComponentInfoFlagsBits long value) {
+            super(value);
+        }
+        @NonNull
+        public static ComponentInfoFlags of(@ComponentInfoFlagsBits long value) {
+            return new ComponentInfoFlags(value);
+        }
+    }
+
+    /**
+     * Specific flags used for retrieving resolve info.
+     */
+    public final static class ResolveInfoFlags extends Flags {
+        private ResolveInfoFlags(@ResolveInfoFlagsBits long value) {
+            super(value);
+        }
+        @NonNull
+        public static ResolveInfoFlags of(@ResolveInfoFlagsBits long value) {
+            return new ResolveInfoFlags(value);
+        }
+    }
+
     /** {@hide} */
     public int getUserId() {
         return UserHandle.myUserId();
@@ -4658,12 +4758,23 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getPackageInfo(String, PackageInfoFlags)} instead.
      */
-    public abstract PackageInfo getPackageInfo(@NonNull String packageName,
-            @PackageInfoFlags int flags)
+    @Deprecated
+    public abstract PackageInfo getPackageInfo(@NonNull String packageName, int flags)
             throws NameNotFoundException;
 
     /**
+     * See {@link #getPackageInfo(String, int)}
+     */
+    @NonNull
+    public PackageInfo getPackageInfo(@NonNull String packageName, @NonNull PackageInfoFlags flags)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getPackageInfo not implemented in subclass");
+    }
+
+    /**
      * Retrieve overall information about an application package that is
      * installed on the system. This method can be used for retrieving
      * information about packages for which multiple versions can be installed
@@ -4684,9 +4795,21 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getPackageInfo(VersionedPackage, PackageInfoFlags)} instead.
      */
+    @Deprecated
     public abstract PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage,
-            @PackageInfoFlags int flags) throws NameNotFoundException;
+            int flags) throws NameNotFoundException;
+
+    /**
+     * See {@link #getPackageInfo(VersionedPackage, int)}
+     */
+    @NonNull
+    public PackageInfo getPackageInfo(@NonNull VersionedPackage versionedPackage,
+            @NonNull PackageInfoFlags flags) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getPackageInfo not implemented in subclass");
+    }
 
     /**
      * Retrieve overall information about an application package that is
@@ -4705,13 +4828,27 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getPackageInfoAsUser(String, PackageInfoFlags, int)} instead.
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @UnsupportedAppUsage
     public abstract PackageInfo getPackageInfoAsUser(@NonNull String packageName,
-            @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+            int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+    /**
+     * See {@link #getPackageInfoAsUser(String, int, int)}
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @NonNull
+    public PackageInfo getPackageInfoAsUser(@NonNull String packageName,
+            @NonNull PackageInfoFlags flags, @UserIdInt int userId) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getPackageInfoAsUser not implemented in subclass");
+    }
 
     /**
      * Map from the current package names in use on the device to whatever
@@ -4834,11 +4971,23 @@
      *         none.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getPackageGids(String, PackageInfoFlags)} instead.
      */
-    public abstract int[] getPackageGids(@NonNull String packageName, @PackageInfoFlags int flags)
+    @Deprecated
+    public abstract int[] getPackageGids(@NonNull String packageName, int flags)
             throws NameNotFoundException;
 
     /**
+     * See {@link #getPackageGids(String, int)}.
+     */
+    @Nullable
+    public int[] getPackageGids(@NonNull String packageName, @NonNull PackageInfoFlags flags)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getPackageGids not implemented in subclass");
+    }
+
+    /**
      * Return the UID associated with the given package name.
      * <p>
      * Note that the same package will have different UIDs under different
@@ -4849,11 +4998,22 @@
      * @return Returns an integer UID who owns the given package name.
      * @throws NameNotFoundException if a package with the given name can not be
      *             found on the system.
+     * @deprecated Use {@link #getPackageUid(String, PackageInfoFlags)} instead.
      */
-    public abstract int getPackageUid(@NonNull String packageName, @PackageInfoFlags int flags)
+    @Deprecated
+    public abstract int getPackageUid(@NonNull String packageName, int flags)
             throws NameNotFoundException;
 
     /**
+     * See {@link #getPackageUid(String, int)}.
+     */
+    public int getPackageUid(@NonNull String packageName, @NonNull PackageInfoFlags flags)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getPackageUid not implemented in subclass");
+    }
+
+    /**
      * Return the UID associated with the given package name.
      * <p>
      * Note that the same package will have different UIDs under different
@@ -4884,12 +5044,24 @@
      * @return Returns an integer UID who owns the given package name.
      * @throws NameNotFoundException if a package with the given name can not be
      *             found on the system.
+     * @deprecated Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} instead.
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @UnsupportedAppUsage
     public abstract int getPackageUidAsUser(@NonNull String packageName,
-            @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+            int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+    /**
+     * See {@link #getPackageUidAsUser(String, int, int)}.
+     * @hide
+     */
+    public int getPackageUidAsUser(@NonNull String packageName, @NonNull PackageInfoFlags flags,
+            @UserIdInt int userId) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getPackageUidAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all of the information we know about a particular permission.
@@ -5015,17 +5187,42 @@
      *         which had been deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getApplicationInfo(String, ApplicationInfoFlags)} instead.
      */
     @NonNull
+    @Deprecated
     public abstract ApplicationInfo getApplicationInfo(@NonNull String packageName,
-            @ApplicationInfoFlags int flags) throws NameNotFoundException;
+            int flags) throws NameNotFoundException;
 
-    /** {@hide} */
+    /**
+     * See {@link #getApplicationInfo(String, int)}.
+     */
+    @NonNull
+    public ApplicationInfo getApplicationInfo(@NonNull String packageName,
+            @NonNull ApplicationInfoFlags flags) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getApplicationInfo not implemented in subclass");
+    }
+
+    /**
+     * @deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, int)} instead.
+     * {@hide}
+     */
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
+    @Deprecated
     public abstract ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
-            @ApplicationInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException;
+            int flags, @UserIdInt int userId) throws NameNotFoundException;
+
+    /** {@hide} */
+    @NonNull
+    public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+            @NonNull ApplicationInfoFlags flags, @UserIdInt int userId)
+            throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getApplicationInfoAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all of the information we know about a particular
@@ -5043,13 +5240,29 @@
      *         which had been deleted with {@code DELETE_KEEP_DATA} flag set).
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getApplicationInfoAsUser(String, ApplicationInfoFlags, UserHandle)}
+     * instead.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    @Deprecated
+    public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+            int flags, @NonNull UserHandle user)
+            throws NameNotFoundException {
+        return getApplicationInfoAsUser(packageName, flags, user.getIdentifier());
+    }
+
+    /**
+     * See {@link #getApplicationInfoAsUser(String, int, UserHandle)}.
      * @hide
      */
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
     public ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
-            @ApplicationInfoFlags int flags, @NonNull UserHandle user)
+            @NonNull ApplicationInfoFlags flags, @NonNull UserHandle user)
             throws NameNotFoundException {
         return getApplicationInfoAsUser(packageName, flags, user.getIdentifier());
     }
@@ -5075,10 +5288,22 @@
      *         activity.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getActivityInfo(ComponentName, ComponentInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component,
-            @ComponentInfoFlags int flags) throws NameNotFoundException;
+            int flags) throws NameNotFoundException;
+
+    /**
+     * See {@link #getActivityInfo(ComponentName, int)}.
+     */
+    @NonNull
+    public ActivityInfo getActivityInfo(@NonNull ComponentName component,
+            @NonNull ComponentInfoFlags flags) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getActivityInfo not implemented in subclass");
+    }
 
     /**
      * Retrieve all of the information we know about a particular receiver
@@ -5092,10 +5317,22 @@
      *         receiver.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getReceiverInfo(ComponentName, ComponentInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract ActivityInfo getReceiverInfo(@NonNull ComponentName component,
-            @ComponentInfoFlags int flags) throws NameNotFoundException;
+            int flags) throws NameNotFoundException;
+
+    /**
+     * See {@link #getReceiverInfo(ComponentName, int)}.
+     */
+    @NonNull
+    public ActivityInfo getReceiverInfo(@NonNull ComponentName component,
+            @NonNull ComponentInfoFlags flags) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getReceiverInfo not implemented in subclass");
+    }
 
     /**
      * Retrieve all of the information we know about a particular service class.
@@ -5107,10 +5344,22 @@
      * @return A {@link ServiceInfo} object containing information about the
      *         service.
      * @throws NameNotFoundException if the component cannot be found on the system.
+     * @deprecated Use {@link #getServiceInfo(ComponentName, ComponentInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract ServiceInfo getServiceInfo(@NonNull ComponentName component,
-            @ComponentInfoFlags int flags) throws NameNotFoundException;
+            int flags) throws NameNotFoundException;
+
+    /**
+     * See {@link #getServiceInfo(ComponentName, int)}.
+     */
+    @NonNull
+    public ServiceInfo getServiceInfo(@NonNull ComponentName component,
+            @NonNull ComponentInfoFlags flags) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getServiceInfo not implemented in subclass");
+    }
 
     /**
      * Retrieve all of the information we know about a particular content
@@ -5124,10 +5373,22 @@
      *         provider.
      * @throws NameNotFoundException if a package with the given name cannot be
      *             found on the system.
+     * @deprecated Use {@link #getProviderInfo(ComponentName, ComponentInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract ProviderInfo getProviderInfo(@NonNull ComponentName component,
-            @ComponentInfoFlags int flags) throws NameNotFoundException;
+            int flags) throws NameNotFoundException;
+
+    /**
+     * See {@link #getProviderInfo(ComponentName, int)}.
+     */
+    @NonNull
+    public ProviderInfo getProviderInfo(@NonNull ComponentName component,
+            @NonNull ComponentInfoFlags flags) throws NameNotFoundException {
+        throw new UnsupportedOperationException(
+                "getProviderInfo not implemented in subclass");
+    }
 
     /**
      * Retrieve information for a particular module.
@@ -5172,9 +5433,21 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
+     * @deprecated Use {@link #getInstalledPackages(PackageInfoFlags)} instead.
+     */
+    @Deprecated
+    @NonNull
+    public abstract List<PackageInfo> getInstalledPackages(int flags);
+
+    /**
+     * See {@link #getInstalledPackages(int)}.
+     * @param flags
      */
     @NonNull
-    public abstract List<PackageInfo> getInstalledPackages(@PackageInfoFlags int flags);
+    public List<PackageInfo> getInstalledPackages(@NonNull PackageInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "getInstalledPackages not implemented in subclass");
+    }
 
     /**
      * Return a List of all installed packages that are currently holding any of
@@ -5190,10 +5463,22 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
+     * @deprecated Use {@link #getPackagesHoldingPermissions(String[], PackageInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract List<PackageInfo> getPackagesHoldingPermissions(
-            @NonNull String[] permissions, @PackageInfoFlags int flags);
+            @NonNull String[] permissions, int flags);
+
+    /**
+     * See {@link #getPackagesHoldingPermissions(String[], int)}.
+     */
+    @NonNull
+    public List<PackageInfo> getPackagesHoldingPermissions(
+            @NonNull String[] permissions, @NonNull PackageInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "getPackagesHoldingPermissions not implemented in subclass");
+    }
 
     /**
      * Return a List of all packages that are installed on the device, for a
@@ -5209,16 +5494,31 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
+     * @deprecated Use {@link #getInstalledPackagesAsUser(PackageInfoFlags, int)} instead.
      * @hide
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @SystemApi
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-    public abstract List<PackageInfo> getInstalledPackagesAsUser(@PackageInfoFlags int flags,
+    public abstract List<PackageInfo> getInstalledPackagesAsUser(int flags,
             @UserIdInt int userId);
 
     /**
+     * See {@link #getInstalledPackagesAsUser(int, int)}.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    public List<PackageInfo> getInstalledPackagesAsUser(@NonNull PackageInfoFlags flags,
+            @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "getApplicationInfoAsUser not implemented in subclass");
+    }
+
+    /**
      * Check whether a particular package has been granted a particular
      * permission.
      *
@@ -5904,11 +6204,22 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
+     * @deprecated  Use {@link #getInstalledApplications(ApplicationInfoFlags)} instead.
      */
     @NonNull
-    public abstract List<ApplicationInfo> getInstalledApplications(@ApplicationInfoFlags int flags);
+    @Deprecated
+    public abstract List<ApplicationInfo> getInstalledApplications(int flags);
 
     /**
+     * See {@link #getInstalledApplications(int)}
+     * @param flags
+     */
+    @NonNull
+    public List<ApplicationInfo> getInstalledApplications(@NonNull ApplicationInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "getInstalledApplications not implemented in subclass");
+    }
+    /**
      * Return a List of all application packages that are installed on the
      * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
      * set, a list of all applications including those deleted with
@@ -5926,13 +6237,27 @@
      *         applications (which includes installed applications as well as
      *         applications with data directory i.e. applications which had been
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
+     * @deprecated  Use {@link #getInstalledApplicationsAsUser(ApplicationInfoFlags, int)} instead.
      * @hide
      */
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @TestApi
+    @Deprecated
     public abstract List<ApplicationInfo> getInstalledApplicationsAsUser(
-            @ApplicationInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #getInstalledApplicationsAsUser(int, int}.
+     * @hide
+     */
+    @NonNull
+    @TestApi
+    public List<ApplicationInfo> getInstalledApplicationsAsUser(
+            @NonNull ApplicationInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "getInstalledApplicationsAsUser not implemented in subclass");
+    }
 
     /**
      * Gets the instant applications the user recently used.
@@ -6081,9 +6406,19 @@
      * @return The shared library list.
      *
      * @see #MATCH_UNINSTALLED_PACKAGES
+     * @deprecated Use {@link #getSharedLibraries(PackageInfoFlags)} instead.
      */
-    public abstract @NonNull List<SharedLibraryInfo> getSharedLibraries(
-            @InstallFlags int flags);
+    @Deprecated
+    public abstract @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags);
+
+    /**
+     * See {@link #getSharedLibraries(int)}.
+     * @param flags
+     */
+    public @NonNull List<SharedLibraryInfo> getSharedLibraries(@NonNull PackageInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "getSharedLibraries() not implemented in subclass");
+    }
 
     /**
      * Get a list of shared libraries on the device.
@@ -6098,10 +6433,22 @@
      * @see #MATCH_UNINSTALLED_PACKAGES
      *
      * @hide
+     * @deprecated Use {@link #getSharedLibrariesAsUser(PackageInfoFlags, int)} instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
-    public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(
-            @InstallFlags int flags, @UserIdInt int userId);
+    public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags,
+            @UserIdInt int userId);
+
+    /**
+     * See {@link #getSharedLibrariesAsUser(int, int)}.
+     * @hide
+     */
+    public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(
+            @NonNull PackageInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "getSharedLibrariesAsUser() not implemented in subclass");
+    }
 
     /**
      * Get the list of shared libraries declared by a package.
@@ -6111,13 +6458,28 @@
      * @return the shared library list
      *
      * @hide
+     * @deprecated Use {@link #getDeclaredSharedLibraries(String, PackageInfoFlags)} instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @RequiresPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES)
     @SystemApi
     public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName,
-            @InstallFlags int flags) {
+            int flags) {
+        throw new UnsupportedOperationException(
+                "getDeclaredSharedLibraries() not implemented in subclass");
+    }
+
+    /**
+     * See {@link #getDeclaredSharedLibraries(String, int)}.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES)
+    @SystemApi
+    public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName,
+            @NonNull PackageInfoFlags flags) {
         throw new UnsupportedOperationException(
                 "getDeclaredSharedLibraries() not implemented in subclass");
     }
@@ -6218,10 +6580,20 @@
      *         matching activity was found. If multiple matching activities are
      *         found and there is no default set, returns a ResolveInfo object
      *         containing something else, such as the activity resolver.
+     * @deprecated Use {@link #resolveActivity(Intent, ResolveInfoFlags)} instead.
+     */
+    @Deprecated
+    @Nullable
+    public abstract ResolveInfo resolveActivity(@NonNull Intent intent, int flags);
+
+    /**
+     * See {@link #resolveActivity(Intent, int)}.
      */
     @Nullable
-    public abstract ResolveInfo resolveActivity(@NonNull Intent intent,
-            @ResolveInfoFlags int flags);
+    public ResolveInfo resolveActivity(@NonNull Intent intent, @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "resolveActivity not implemented in subclass");
+    }
 
     /**
      * Determine the best action to perform for a given Intent for a given user.
@@ -6250,12 +6622,25 @@
      *         found and there is no default set, returns a ResolveInfo object
      *         containing something else, such as the activity resolver.
      * @hide
+     * @deprecated Use {@link #resolveActivityAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @Nullable
     @UnsupportedAppUsage
     public abstract ResolveInfo resolveActivityAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #resolveActivityAsUser(Intent, int, int)}.
+     * @hide
+     */
+    @Nullable
+    public ResolveInfo resolveActivityAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "resolveActivityAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all activities that can be performed for the given intent.
@@ -6271,10 +6656,21 @@
      *         words, the first item is what would be returned by
      *         {@link #resolveActivity}. If there are no matching activities, an
      *         empty list is returned.
+     * @deprecated Use {@link #queryIntentActivities(Intent, ResolveInfoFlags)} instead.
+     */
+    @Deprecated
+    @NonNull
+    public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent, int flags);
+
+    /**
+     * See {@link #queryIntentActivities(Intent, int)}.
      */
     @NonNull
-    public abstract List<ResolveInfo> queryIntentActivities(@NonNull Intent intent,
-            @ResolveInfoFlags int flags);
+    public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "queryIntentActivities not implemented in subclass");
+    }
 
     /**
      * Retrieve all activities that can be performed for the given intent, for a
@@ -6292,12 +6688,25 @@
      *         {@link #resolveActivity}. If there are no matching activities, an
      *         empty list is returned.
      * @hide
+     * @deprecated Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
     public abstract List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #queryIntentActivitiesAsUser(Intent, int, int)}.
+     * @hide
+     */
+    @NonNull
+    public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "queryIntentActivitiesAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all activities that can be performed for the given intent, for a
@@ -6316,13 +6725,28 @@
      *         {@link #resolveActivity}. If there are no matching activities, an
      *         empty list is returned.
      * @hide
+     * @deprecated Use {@link #queryIntentActivitiesAsUser(Intent, ResolveInfoFlags, UserHandle)}
+     * instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
     public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+            int flags, @NonNull UserHandle user) {
+        return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier());
+    }
+
+    /**
+     * See {@link #queryIntentActivitiesAsUser(Intent, int, UserHandle)}.
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @NonNull UserHandle user) {
         return queryIntentActivitiesAsUser(intent, flags, user.getIdentifier());
     }
 
@@ -6350,10 +6774,24 @@
      *         activities that can handle <var>intent</var> but did not get
      *         included by one of the <var>specifics</var> intents. If there are
      *         no matching activities, an empty list is returned.
+     * @deprecated Use {@link #queryIntentActivityOptions(ComponentName, List, Intent,
+     * ResolveInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
-            @Nullable Intent[] specifics, @NonNull Intent intent, @ResolveInfoFlags int flags);
+            @Nullable Intent[] specifics, @NonNull Intent intent, int flags);
+
+    /**
+     * See {@link #queryIntentActivityOptions(ComponentName, Intent[], Intent, int)}.
+     */
+    @NonNull
+    public List<ResolveInfo> queryIntentActivityOptions(@Nullable ComponentName caller,
+            @Nullable List<Intent> specifics, @NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "queryIntentActivityOptions not implemented in subclass");
+    }
 
     /**
      * Retrieve all receivers that can handle a broadcast of the given intent.
@@ -6363,10 +6801,21 @@
      * @return Returns a List of ResolveInfo objects containing one entry for
      *         each matching receiver, ordered from best to worst. If there are
      *         no matching receivers, an empty list or null is returned.
+     * @deprecated Use {@link #queryBroadcastReceivers(Intent, ResolveInfoFlags)} instead.
+     */
+    @Deprecated
+    @NonNull
+    public abstract List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent, int flags);
+
+    /**
+     * See {@link #queryBroadcastReceivers(Intent, int)}.
      */
     @NonNull
-    public abstract List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent,
-            @ResolveInfoFlags int flags);
+    public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "queryBroadcastReceivers not implemented in subclass");
+    }
 
     /**
      * Retrieve all receivers that can handle a broadcast of the given intent,
@@ -6379,24 +6828,53 @@
      *         each matching receiver, ordered from best to worst. If there are
      *         no matching receivers, an empty list or null is returned.
      * @hide
+     * @deprecated Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, UserHandle)}
+     * instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @SystemApi
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, UserHandle userHandle) {
+            int flags, UserHandle userHandle) {
+        return queryBroadcastReceiversAsUser(intent, flags, userHandle.getIdentifier());
+    }
+
+    /**
+     * See {@link #queryBroadcastReceiversAsUser(Intent, int, UserHandle)}.
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @NonNull UserHandle userHandle) {
         return queryBroadcastReceiversAsUser(intent, flags, userHandle.getIdentifier());
     }
 
     /**
      * @hide
+     * @deprecated Use {@link #queryBroadcastReceiversAsUser(Intent, ResolveInfoFlags, int)}
+     * instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
     public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #queryBroadcastReceiversAsUser(Intent, int, int)}.
+     * @hide
+     */
+    @NonNull
+    public List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "queryBroadcastReceiversAsUser not implemented in subclass");
+    }
 
 
     /** @deprecated @hide */
@@ -6404,7 +6882,7 @@
     @Deprecated
     @UnsupportedAppUsage
     public List<ResolveInfo> queryBroadcastReceivers(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @UserIdInt int userId) {
+            int flags, @UserIdInt int userId) {
         final String msg = "Shame on you for calling the hidden API "
                 + "queryBroadcastReceivers(). Shame!";
         if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.O) {
@@ -6424,17 +6902,41 @@
      * @return Returns a ResolveInfo object containing the final service intent
      *         that was determined to be the best action. Returns null if no
      *         matching service was found.
+     * @deprecated Use {@link #resolveService(Intent, ResolveInfoFlags)} instead.
+     */
+    @Deprecated
+    @Nullable
+    public abstract ResolveInfo resolveService(@NonNull Intent intent, int flags);
+
+    /**
+     * See {@link #resolveService(Intent, int)}.
      */
     @Nullable
-    public abstract ResolveInfo resolveService(@NonNull Intent intent, @ResolveInfoFlags int flags);
+    public ResolveInfo resolveService(@NonNull Intent intent, @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "resolveService not implemented in subclass");
+    }
 
     /**
      * @hide
+     * @deprecated Use {@link #resolveServiceAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @Nullable
     public abstract ResolveInfo resolveServiceAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #resolveServiceAsUser(Intent, int, int)}.
+     * @hide
+     */
+    @Nullable
+    public ResolveInfo resolveServiceAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "resolveServiceAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all services that can match the given intent.
@@ -6446,10 +6948,22 @@
      *         words, the first item is what would be returned by
      *         {@link #resolveService}. If there are no matching services, an
      *         empty list or null is returned.
+     * @deprecated Use {@link #queryIntentServices(Intent, ResolveInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentServices(@NonNull Intent intent,
-            @ResolveInfoFlags int flags);
+            int flags);
+
+    /**
+     * See {@link #queryIntentServices(Intent, int)}.
+     */
+    @NonNull
+    public List<ResolveInfo> queryIntentServices(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "queryIntentServices not implemented in subclass");
+    }
 
     /**
      * Retrieve all services that can match the given intent for a given user.
@@ -6463,12 +6977,25 @@
      *         {@link #resolveService}. If there are no matching services, an
      *         empty list or null is returned.
      * @hide
+     * @deprecated Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, int)} instead.
      */
+    @Deprecated
     @SuppressWarnings("HiddenAbstractMethod")
     @NonNull
     @UnsupportedAppUsage
     public abstract List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #queryIntentServicesAsUser(Intent, int, int)}.
+     * @hide
+     */
+    @NonNull
+    public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "queryIntentServicesAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all services that can match the given intent for a given user.
@@ -6482,14 +7009,60 @@
      *         {@link #resolveService}. If there are no matching services, an
      *         empty list or null is returned.
      * @hide
+     * @deprecated Use {@link #queryIntentServicesAsUser(Intent, ResolveInfoFlags, UserHandle)}
+     * instead.
+     */
+    @Deprecated
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+            int flags, @NonNull UserHandle user) {
+        return queryIntentServicesAsUser(intent, flags, user.getIdentifier());
+    }
+
+    /**
+     * See {@link #queryIntentServicesAsUser(Intent, int, UserHandle)}.
+     * @hide
      */
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
     public List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+            @NonNull ResolveInfoFlags flags, @NonNull UserHandle user) {
         return queryIntentServicesAsUser(intent, flags, user.getIdentifier());
     }
+    /**
+     * Retrieve all providers that can match the given intent.
+     *
+     * @param intent An intent containing all of the desired specification
+     *            (action, data, type, category, and/or component).
+     * @param flags Additional option flags to modify the data returned.
+     * @param userId The user id.
+     * @return Returns a List of ResolveInfo objects containing one entry for
+     *         each matching provider, ordered from best to worst. If there are
+     *         no matching services, an empty list or null is returned.
+     * @hide
+     * @deprecated Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags, int)}
+     * instead.
+     */
+    @Deprecated
+    @SuppressWarnings("HiddenAbstractMethod")
+    @NonNull
+    @UnsupportedAppUsage
+    public abstract List<ResolveInfo> queryIntentContentProvidersAsUser(
+            @NonNull Intent intent, int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #queryIntentContentProvidersAsUser(Intent, int, int)}.
+     * @hide
+     */
+    @NonNull
+    protected List<ResolveInfo> queryIntentContentProvidersAsUser(
+            @NonNull Intent intent, @NonNull ResolveInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "queryIntentContentProvidersAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve all providers that can match the given intent.
@@ -6497,35 +7070,32 @@
      * @param intent An intent containing all of the desired specification
      *            (action, data, type, category, and/or component).
      * @param flags Additional option flags to modify the data returned.
-     * @param userId The user id.
-     * @return Returns a List of ResolveInfo objects containing one entry for
-     *         each matching provider, ordered from best to worst. If there are
-     *         no matching services, an empty list or null is returned.
-     * @hide
-     */
-    @SuppressWarnings("HiddenAbstractMethod")
-    @NonNull
-    @UnsupportedAppUsage
-    public abstract List<ResolveInfo> queryIntentContentProvidersAsUser(
-            @NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId);
-
-    /**
-     * Retrieve all providers that can match the given intent.
-     *
-     * @param intent An intent containing all of the desired specification
-     *            (action, data, type, category, and/or component).
-     * @param flags Additional option flags to modify the data returned.
      * @param user The user being queried.
      * @return Returns a List of ResolveInfo objects containing one entry for
      *         each matching provider, ordered from best to worst. If there are
      *         no matching services, an empty list or null is returned.
      * @hide
+     * @deprecated Use {@link #queryIntentContentProvidersAsUser(Intent, ResolveInfoFlags,
+     * UserHandle)} instead.
+     */
+    @Deprecated
+    @NonNull
+    @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
+    @SystemApi
+    public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
+            int flags, @NonNull UserHandle user) {
+        return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier());
+    }
+
+    /**
+     * See {@link #queryIntentContentProvidersAsUser(Intent, int, UserHandle)}.
+     * @hide
      */
     @NonNull
     @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
     @SystemApi
     public List<ResolveInfo> queryIntentContentProvidersAsUser(@NonNull Intent intent,
-            @ResolveInfoFlags int flags, @NonNull UserHandle user) {
+            @NonNull ResolveInfoFlags flags, @NonNull UserHandle user) {
         return queryIntentContentProvidersAsUser(intent, flags, user.getIdentifier());
     }
 
@@ -6538,10 +7108,22 @@
      * @return Returns a List of ResolveInfo objects containing one entry for
      *         each matching provider, ordered from best to worst. If there are
      *         no matching services, an empty list or null is returned.
+     * @deprecated Use {@link #queryIntentContentProviders(Intent, ResolveInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent,
-            @ResolveInfoFlags int flags);
+            int flags);
+
+    /**
+     * See {@link #queryIntentContentProviders(Intent, int)}.
+     */
+    @NonNull
+    public List<ResolveInfo> queryIntentContentProviders(@NonNull Intent intent,
+            @NonNull ResolveInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "queryIntentContentProviders not implemented in subclass");
+    }
 
     /**
      * Find a single content provider by its authority.
@@ -6556,10 +7138,22 @@
      * @param flags Additional option flags to modify the data returned.
      * @return A {@link ProviderInfo} object containing information about the
      *         provider. If a provider was not found, returns null.
+     * @deprecated Use {@link #resolveContentProvider(String, ComponentInfoFlags)} instead.
      */
+    @Deprecated
     @Nullable
     public abstract ProviderInfo resolveContentProvider(@NonNull String authority,
-            @ComponentInfoFlags int flags);
+            int flags);
+
+    /**
+     * See {@link #resolveContentProvider(String, int)}.
+     */
+    @Nullable
+    public ProviderInfo resolveContentProvider(@NonNull String authority,
+            @NonNull ComponentInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "resolveContentProvider not implemented in subclass");
+    }
 
     /**
      * Find a single content provider by its base path name.
@@ -6570,12 +7164,25 @@
      * @return A {@link ProviderInfo} object containing information about the
      *         provider. If a provider was not found, returns null.
      * @hide
+     * @deprecated Use {@link #resolveContentProviderAsUser(String, ComponentInfoFlags, int)}
+     * instead.
      */
     @SuppressWarnings("HiddenAbstractMethod")
     @Nullable
     @UnsupportedAppUsage
     public abstract ProviderInfo resolveContentProviderAsUser(@NonNull String providerName,
-            @ComponentInfoFlags int flags, @UserIdInt int userId);
+            int flags, @UserIdInt int userId);
+
+    /**
+     * See {@link #resolveContentProviderAsUser(String, int, int)}.
+     * @hide
+     */
+    @Nullable
+    public ProviderInfo resolveContentProviderAsUser(@NonNull String providerName,
+            @NonNull ComponentInfoFlags flags, @UserIdInt int userId) {
+        throw new UnsupportedOperationException(
+                "resolveContentProviderAsUser not implemented in subclass");
+    }
 
     /**
      * Retrieve content provider information.
@@ -6593,10 +7200,22 @@
      *         each provider either matching <var>processName</var> or, if
      *         <var>processName</var> is null, all known content providers.
      *         <em>If there are no matching providers, null is returned.</em>
+     * @deprecated Use {@link #queryContentProviders(String, int, ComponentInfoFlags)} instead.
      */
+    @Deprecated
     @NonNull
     public abstract List<ProviderInfo> queryContentProviders(
-            @Nullable String processName, int uid, @ComponentInfoFlags int flags);
+            @Nullable String processName, int uid, int flags);
+
+    /**
+     * See {@link #queryContentProviders(String, int, int)}.
+     */
+    @NonNull
+    public List<ProviderInfo> queryContentProviders(
+            @Nullable String processName, int uid, @NonNull ComponentInfoFlags flags) {
+        throw new UnsupportedOperationException(
+                "queryContentProviders not implemented in subclass");
+    }
 
     /**
      * Same as {@link #queryContentProviders}, except when {@code metaDataKey} is not null,
@@ -6612,10 +7231,24 @@
      * {@link #queryIntentContentProviders} for that.
      *
      * @hide
+     * @deprecated Use {@link #queryContentProviders(String, int, ComponentInfoFlags, String)}
+     * instead.
+     */
+    @Deprecated
+    @NonNull
+    public List<ProviderInfo> queryContentProviders(@Nullable String processName,
+            int uid, int flags, String metaDataKey) {
+        // Provide the default implementation for mocks.
+        return queryContentProviders(processName, uid, flags);
+    }
+
+    /**
+     * See {@link #queryContentProviders(String, int, int, String)}.
+     * @hide
      */
     @NonNull
     public List<ProviderInfo> queryContentProviders(@Nullable String processName,
-            int uid, @ComponentInfoFlags int flags, String metaDataKey) {
+            int uid, @NonNull ComponentInfoFlags flags, @Nullable String metaDataKey) {
         // Provide the default implementation for mocks.
         return queryContentProviders(processName, uid, flags);
     }
@@ -7151,10 +7784,21 @@
      * @param flags Additional option flags to modify the data returned.
      * @return A PackageInfo object containing information about the package
      *         archive. If the package could not be parsed, returns null.
+     * @deprecated Use {@link #getPackageArchiveInfo(String, PackageInfoFlags)} instead.
+     */
+    @Deprecated
+    @Nullable
+    public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath, int flags) {
+        throw new UnsupportedOperationException(
+                "getPackageArchiveInfo() not implemented in subclass");
+    }
+
+    /**
+     * See {@link #getPackageArchiveInfo(String, int)}.
      */
     @Nullable
     public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
-            @PackageInfoFlags int flags) {
+            @NonNull PackageInfoFlags flags) {
         throw new UnsupportedOperationException(
                 "getPackageArchiveInfo() not implemented in subclass");
     }
@@ -7730,7 +8374,7 @@
      */
     @NonNull
     @Deprecated
-    public abstract List<PackageInfo> getPreferredPackages(@PackageInfoFlags int flags);
+    public abstract List<PackageInfo> getPreferredPackages(int flags);
 
     /**
      * Add a new preferred activity mapping to the system.  This will be used
@@ -9406,10 +10050,11 @@
 
     private static final class ApplicationInfoQuery {
         final String packageName;
-        final int flags;
+        final long flags;
         final int userId;
 
-        ApplicationInfoQuery(@Nullable String packageName, int flags, int userId) {
+        ApplicationInfoQuery(@Nullable String packageName, @ApplicationInfoFlagsBits long flags,
+                int userId) {
             this.packageName = packageName;
             this.flags = flags;
             this.userId = userId;
@@ -9448,7 +10093,7 @@
     }
 
     private static ApplicationInfo getApplicationInfoAsUserUncached(
-            String packageName, int flags, int userId) {
+            String packageName, @ApplicationInfoFlagsBits long flags, int userId) {
         try {
             return ActivityThread.getPackageManager()
                     .getApplicationInfo(packageName, flags, userId);
@@ -9478,7 +10123,7 @@
 
     /** @hide */
     public static ApplicationInfo getApplicationInfoAsUserCached(
-            String packageName, int flags, int userId) {
+            String packageName, @ApplicationInfoFlagsBits long flags, int userId) {
         return sApplicationInfoCache.query(
                 new ApplicationInfoQuery(packageName, flags, userId));
     }
@@ -9509,10 +10154,10 @@
 
     private static final class PackageInfoQuery {
         final String packageName;
-        final int flags;
+        final long flags;
         final int userId;
 
-        PackageInfoQuery(@Nullable String packageName, int flags, int userId) {
+        PackageInfoQuery(@Nullable String packageName, @PackageInfoFlagsBits long flags, int userId) {
             this.packageName = packageName;
             this.flags = flags;
             this.userId = userId;
@@ -9551,7 +10196,7 @@
     }
 
     private static PackageInfo getPackageInfoAsUserUncached(
-            String packageName, int flags, int userId) {
+            String packageName, @PackageInfoFlagsBits long flags, int userId) {
         try {
             return ActivityThread.getPackageManager().getPackageInfo(packageName, flags, userId);
         } catch (RemoteException e) {
@@ -9580,7 +10225,7 @@
 
     /** @hide */
     public static PackageInfo getPackageInfoAsUserCached(
-            String packageName, int flags, int userId) {
+            String packageName, @PackageInfoFlagsBits long flags, int userId) {
         return sPackageInfoCache.query(new PackageInfoQuery(packageName, flags, userId));
     }
 
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e2c91a4b..f31f78f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7297,7 +7297,7 @@
             splitFlags = dest.createIntArray();
             splitPrivateFlags = dest.createIntArray();
             baseHardwareAccelerated = (dest.readInt() == 1);
-            applicationInfo = dest.readParcelable(boot);
+            applicationInfo = dest.readParcelable(boot, android.content.pm.ApplicationInfo.class);
             if (applicationInfo.permission != null) {
                 applicationInfo.permission = applicationInfo.permission.intern();
             }
@@ -7305,19 +7305,19 @@
             // We don't serialize the "owner" package and the application info object for each of
             // these components, in order to save space and to avoid circular dependencies while
             // serialization. We need to fix them all up here.
-            dest.readParcelableList(permissions, boot);
+            dest.readParcelableList(permissions, boot, android.content.pm.PackageParser.Permission.class);
             fixupOwner(permissions);
-            dest.readParcelableList(permissionGroups, boot);
+            dest.readParcelableList(permissionGroups, boot, android.content.pm.PackageParser.PermissionGroup.class);
             fixupOwner(permissionGroups);
-            dest.readParcelableList(activities, boot);
+            dest.readParcelableList(activities, boot, android.content.pm.PackageParser.Activity.class);
             fixupOwner(activities);
-            dest.readParcelableList(receivers, boot);
+            dest.readParcelableList(receivers, boot, android.content.pm.PackageParser.Activity.class);
             fixupOwner(receivers);
-            dest.readParcelableList(providers, boot);
+            dest.readParcelableList(providers, boot, android.content.pm.PackageParser.Provider.class);
             fixupOwner(providers);
-            dest.readParcelableList(services, boot);
+            dest.readParcelableList(services, boot, android.content.pm.PackageParser.Service.class);
             fixupOwner(services);
-            dest.readParcelableList(instrumentation, boot);
+            dest.readParcelableList(instrumentation, boot, android.content.pm.PackageParser.Instrumentation.class);
             fixupOwner(instrumentation);
 
             dest.readStringList(requestedPermissions);
@@ -7327,10 +7327,10 @@
             protectedBroadcasts = dest.createStringArrayList();
             internStringArrayList(protectedBroadcasts);
 
-            parentPackage = dest.readParcelable(boot);
+            parentPackage = dest.readParcelable(boot, android.content.pm.PackageParser.Package.class);
 
             childPackages = new ArrayList<>();
-            dest.readParcelableList(childPackages, boot);
+            dest.readParcelableList(childPackages, boot, android.content.pm.PackageParser.Package.class);
             if (childPackages.size() == 0) {
                 childPackages = null;
             }
@@ -7364,7 +7364,7 @@
             }
 
             preferredActivityFilters = new ArrayList<>();
-            dest.readParcelableList(preferredActivityFilters, boot);
+            dest.readParcelableList(preferredActivityFilters, boot, android.content.pm.PackageParser.ActivityIntentInfo.class);
             if (preferredActivityFilters.size() == 0) {
                 preferredActivityFilters = null;
             }
@@ -7385,7 +7385,7 @@
             }
             mSharedUserLabel = dest.readInt();
 
-            mSigningDetails = dest.readParcelable(boot);
+            mSigningDetails = dest.readParcelable(boot, android.content.pm.PackageParser.SigningDetails.class);
 
             mPreferredOrder = dest.readInt();
 
@@ -7397,19 +7397,19 @@
 
 
             configPreferences = new ArrayList<>();
-            dest.readParcelableList(configPreferences, boot);
+            dest.readParcelableList(configPreferences, boot, android.content.pm.ConfigurationInfo.class);
             if (configPreferences.size() == 0) {
                 configPreferences = null;
             }
 
             reqFeatures = new ArrayList<>();
-            dest.readParcelableList(reqFeatures, boot);
+            dest.readParcelableList(reqFeatures, boot, android.content.pm.FeatureInfo.class);
             if (reqFeatures.size() == 0) {
                 reqFeatures = null;
             }
 
             featureGroups = new ArrayList<>();
-            dest.readParcelableList(featureGroups, boot);
+            dest.readParcelableList(featureGroups, boot, android.content.pm.FeatureGroupInfo.class);
             if (featureGroups.size() == 0) {
                 featureGroups = null;
             }
@@ -7806,13 +7806,13 @@
         private Permission(Parcel in) {
             super(in);
             final ClassLoader boot = Object.class.getClassLoader();
-            info = in.readParcelable(boot);
+            info = in.readParcelable(boot, android.content.pm.PermissionInfo.class);
             if (info.group != null) {
                 info.group = info.group.intern();
             }
 
             tree = (in.readInt() == 1);
-            group = in.readParcelable(boot);
+            group = in.readParcelable(boot, android.content.pm.PackageParser.PermissionGroup.class);
         }
 
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<Permission>() {
@@ -7867,7 +7867,7 @@
 
         private PermissionGroup(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.PermissionGroupInfo.class);
         }
 
         public static final Parcelable.Creator CREATOR = new Parcelable.Creator<PermissionGroup>() {
@@ -8160,7 +8160,7 @@
 
         private Activity(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ActivityInfo.class);
             mHasMaxAspectRatio = in.readBoolean();
             mHasMinAspectRatio = in.readBoolean();
 
@@ -8254,7 +8254,7 @@
 
         private Service(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ServiceInfo.class);
 
             for (ServiceIntentInfo aii : intents) {
                 aii.service = this;
@@ -8344,7 +8344,7 @@
 
         private Provider(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.ProviderInfo.class);
             syncable = (in.readInt() == 1);
 
             for (ProviderIntentInfo aii : intents) {
@@ -8436,7 +8436,7 @@
 
         private Instrumentation(Parcel in) {
             super(in);
-            info = in.readParcelable(Object.class.getClassLoader());
+            info = in.readParcelable(Object.class.getClassLoader(), android.content.pm.InstrumentationInfo.class);
 
             if (info.targetPackage != null) {
                 info.targetPackage = info.targetPackage.intern();
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 4ba2ee6..3443c75 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -134,8 +134,8 @@
         mName = parcel.readString8();
         mVersion = parcel.readLong();
         mType = parcel.readInt();
-        mDeclaringPackage = parcel.readParcelable(null);
-        mDependentPackages = parcel.readArrayList(null);
+        mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class);
+        mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
         mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
         mIsNative = parcel.readBoolean();
     }
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index a264beb..7d4f7ec 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -352,6 +352,16 @@
         return disabledReason >= DISABLED_REASON_RESTORE_ISSUE_START;
     }
 
+    /** @hide */
+    @IntDef(flag = true, value = {SURFACE_LAUNCHER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Surface {}
+
+    /**
+     * Indicates system surfaces managed by a launcher app. e.g. Long-Press Menu.
+     */
+    public static final int SURFACE_LAUNCHER = 1 << 0;
+
     /**
      * Shortcut category for messaging related actions, such as chat.
      */
@@ -451,6 +461,8 @@
 
     @Nullable private String mStartingThemeResName;
 
+    private int mExcludedSurfaces;
+
     private ShortcutInfo(Builder b) {
         mUserId = b.mContext.getUserId();
 
@@ -474,6 +486,7 @@
         if (b.mIsLongLived) {
             setLongLived();
         }
+        mExcludedSurfaces = b.mExcludedSurfaces;
         mRank = b.mRank;
         mExtras = b.mExtras;
         mLocusId = b.mLocusId;
@@ -587,6 +600,7 @@
         mLastChangedTimestamp = source.mLastChangedTimestamp;
         mDisabledReason = source.mDisabledReason;
         mLocusId = source.mLocusId;
+        mExcludedSurfaces = source.mExcludedSurfaces;
 
         // Just always keep it since it's cheep.
         mIconResId = source.mIconResId;
@@ -1025,6 +1039,8 @@
 
         private int mStartingThemeResId;
 
+        private int mExcludedSurfaces;
+
         /**
          * Old style constructor.
          * @hide
@@ -1385,6 +1401,22 @@
         }
 
         /**
+         * Sets which surfaces a shortcut will be excluded from.
+         *
+         * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be
+         * excluded from the search result of {@link android.content.pm.LauncherApps#getShortcuts(
+         * android.content.pm.LauncherApps.ShortcutQuery, UserHandle)} nor
+         * {@link android.content.pm.ShortcutManager#getShortcuts(int)}. This generally means the
+         * shortcut would not be displayed by a launcher app (e.g. in Long-Press menu), while
+         * remain visible in other surfaces such as assistant or on-device-intelligence.
+         */
+        @NonNull
+        public Builder setExcludedFromSurfaces(final int surfaces) {
+            mExcludedSurfaces = surfaces;
+            return this;
+        }
+
+        /**
          * Creates a {@link ShortcutInfo} instance.
          */
         @NonNull
@@ -2137,13 +2169,20 @@
         mCategories = cloneCategories(categories);
     }
 
+    /**
+     * Return true if the shortcut is included in specified surface.
+     */
+    public boolean isIncludedIn(@Surface int surface) {
+        return (mExcludedSurfaces & surface) == 0;
+    }
+
     private ShortcutInfo(Parcel source) {
         final ClassLoader cl = getClass().getClassLoader();
 
         mUserId = source.readInt();
         mId = source.readString8();
         mPackageName = source.readString8();
-        mActivity = source.readParcelable(cl);
+        mActivity = source.readParcelable(cl, android.content.ComponentName.class);
         mFlags = source.readInt();
         mIconResId = source.readInt();
         mLastChangedTimestamp = source.readLong();
@@ -2153,7 +2192,7 @@
             return; // key information only.
         }
 
-        mIcon = source.readParcelable(cl);
+        mIcon = source.readParcelable(cl, android.graphics.drawable.Icon.class);
         mTitle = source.readCharSequence();
         mTitleResId = source.readInt();
         mText = source.readCharSequence();
@@ -2163,7 +2202,7 @@
         mIntents = source.readParcelableArray(cl, Intent.class);
         mIntentPersistableExtrases = source.readParcelableArray(cl, PersistableBundle.class);
         mRank = source.readInt();
-        mExtras = source.readParcelable(cl);
+        mExtras = source.readParcelable(cl, android.os.PersistableBundle.class);
         mBitmapPath = source.readString8();
 
         mIconResName = source.readString8();
@@ -2182,9 +2221,10 @@
         }
 
         mPersons = source.readParcelableArray(cl, Person.class);
-        mLocusId = source.readParcelable(cl);
+        mLocusId = source.readParcelable(cl, android.content.LocusId.class);
         mIconUri = source.readString8();
         mStartingThemeResName = source.readString8();
+        mExcludedSurfaces = source.readInt();
     }
 
     @Override
@@ -2237,6 +2277,7 @@
         dest.writeParcelable(mLocusId, flags);
         dest.writeString8(mIconUri);
         dest.writeString8(mStartingThemeResName);
+        dest.writeInt(mExcludedSurfaces);
     }
 
     public static final @NonNull Creator<ShortcutInfo> CREATOR =
@@ -2346,6 +2387,9 @@
         if (isLongLived()) {
             sb.append("Liv");
         }
+        if (!isIncludedIn(SURFACE_LAUNCHER)) {
+            sb.append("Hid-L");
+        }
         sb.append("]");
 
         addIndentOrComma(sb, indent);
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index be0d934..7dbfd08 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -704,8 +704,8 @@
         }
 
         private ShareShortcutInfo(@NonNull Parcel in) {
-            mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
-            mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader());
+            mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
+            mTargetComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
         }
 
         @NonNull
diff --git a/core/java/android/content/pm/ShortcutQueryWrapper.java b/core/java/android/content/pm/ShortcutQueryWrapper.java
index c613441..64337d8 100644
--- a/core/java/android/content/pm/ShortcutQueryWrapper.java
+++ b/core/java/android/content/pm/ShortcutQueryWrapper.java
@@ -143,7 +143,7 @@
         List<LocusId> locusIds = null;
         if ((flg & 0x8) != 0) {
             locusIds = new ArrayList<>();
-            in.readParcelableList(locusIds, LocusId.class.getClassLoader());
+            in.readParcelableList(locusIds, LocusId.class.getClassLoader(), android.content.LocusId.class);
         }
         ComponentName activity = (flg & 0x10) == 0 ? null
                 : (ComponentName) in.readTypedObject(ComponentName.CREATOR);
diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java
index 3e69ff5..868bb9c 100644
--- a/core/java/android/content/pm/VerifierInfo.java
+++ b/core/java/android/content/pm/VerifierInfo.java
@@ -59,7 +59,7 @@
 
     private VerifierInfo(Parcel source) {
         packageName = source.readString();
-        publicKey = (PublicKey) source.readSerializable();
+        publicKey = (PublicKey) source.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class);
     }
 
     @Override
diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
index b11b38a..28290d7 100644
--- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java
@@ -76,8 +76,9 @@
 
     @Nullable
     public static PackageInfo generate(ParsingPackageRead pkg, int[] gids,
-            @PackageManager.PackageInfoFlags long flags, long firstInstallTime, long lastUpdateTime,
-            Set<String> grantedPermissions, FrameworkPackageUserState state, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
+            long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state,
+            int userId) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions,
                 state, userId, null);
     }
@@ -90,9 +91,9 @@
 
     @Nullable
     private static PackageInfo generateWithComponents(ParsingPackageRead pkg, int[] gids,
-            @PackageManager.PackageInfoFlags long flags, long firstInstallTime, long lastUpdateTime,
-            Set<String> grantedPermissions, FrameworkPackageUserState state, int userId,
-            @Nullable ApexInfo apexInfo) {
+            @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
+            long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state,
+            int userId, @Nullable ApexInfo apexInfo) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId);
         if (applicationInfo == null) {
             return null;
@@ -190,9 +191,9 @@
 
     @Nullable
     public static PackageInfo generateWithoutComponents(ParsingPackageRead pkg, int[] gids,
-            @PackageManager.PackageInfoFlags long flags, long firstInstallTime, long lastUpdateTime,
-            Set<String> grantedPermissions, FrameworkPackageUserState state, int userId,
-            @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) {
+            @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
+            long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state,
+            int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) {
         if (!checkUseInstalled(pkg, state, flags)) {
             return null;
         }
@@ -210,7 +211,7 @@
      */
     @NonNull
     public static PackageInfo generateWithoutComponentsUnchecked(ParsingPackageRead pkg, int[] gids,
-            @PackageManager.PackageInfoFlags long flags, long firstInstallTime,
+            @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, FrameworkPackageUserState state,
             int userId, @Nullable ApexInfo apexInfo, @NonNull ApplicationInfo applicationInfo) {
         PackageInfo pi = new PackageInfo();
@@ -365,7 +366,7 @@
 
     @Nullable
     public static ApplicationInfo generateApplicationInfo(ParsingPackageRead pkg,
-            @PackageManager.ApplicationInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ApplicationInfoFlagsBits long flags, FrameworkPackageUserState state,
             int userId) {
         if (pkg == null) {
             return null;
@@ -393,7 +394,7 @@
      */
     @NonNull
     public static ApplicationInfo generateApplicationInfoUnchecked(@NonNull ParsingPackageRead pkg,
-            @PackageManager.ApplicationInfoFlags long flags,
+            @PackageManager.ApplicationInfoFlagsBits long flags,
             @NonNull FrameworkPackageUserState state, int userId, boolean assignUserFields) {
         // Make shallow copy so we can store the metadata/libraries safely
         ApplicationInfo ai = ((ParsingPackageHidden) pkg).toAppInfoWithoutState();
@@ -453,7 +454,7 @@
 
     @Nullable
     public static ApplicationInfo generateDelegateApplicationInfo(@Nullable ApplicationInfo ai,
-            @PackageManager.ApplicationInfoFlags long flags,
+            @PackageManager.ApplicationInfoFlagsBits long flags,
             @NonNull FrameworkPackageUserState state, int userId) {
         if (ai == null || !checkUseInstalledOrHidden(flags, state, ai)) {
             return null;
@@ -470,8 +471,8 @@
 
     @Nullable
     public static ActivityInfo generateDelegateActivityInfo(@Nullable ActivityInfo a,
-            @PackageManager.ComponentInfoFlags long flags, @NonNull FrameworkPackageUserState state,
-            int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags,
+            @NonNull FrameworkPackageUserState state, int userId) {
         if (a == null || !checkUseInstalledOrHidden(flags, state, a.applicationInfo)) {
             return null;
         }
@@ -485,7 +486,7 @@
 
     @Nullable
     public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state,
             @Nullable ApplicationInfo applicationInfo, int userId) {
         if (a == null) return null;
         if (!checkUseInstalled(pkg, state, flags)) {
@@ -510,7 +511,7 @@
      */
     @NonNull
     public static ActivityInfo generateActivityInfoUnchecked(@NonNull ParsedActivity a,
-            @PackageManager.ComponentInfoFlags long flags,
+            @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull ApplicationInfo applicationInfo) {
         // Make shallow copies so we can store the metadata safely
         ActivityInfo ai = new ActivityInfo();
@@ -551,14 +552,14 @@
 
     @Nullable
     public static ActivityInfo generateActivityInfo(ParsingPackageRead pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state,
             int userId) {
         return generateActivityInfo(pkg, a, flags, state, null, userId);
     }
 
     @Nullable
     public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state,
             @Nullable ApplicationInfo applicationInfo, int userId) {
         if (s == null) return null;
         if (!checkUseInstalled(pkg, state, flags)) {
@@ -583,7 +584,7 @@
      */
     @NonNull
     public static ServiceInfo generateServiceInfoUnchecked(@NonNull ParsedService s,
-            @PackageManager.ComponentInfoFlags long flags,
+            @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull ApplicationInfo applicationInfo) {
         // Make shallow copies so we can store the metadata safely
         ServiceInfo si = new ServiceInfo();
@@ -602,14 +603,14 @@
 
     @Nullable
     public static ServiceInfo generateServiceInfo(ParsingPackageRead pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state,
             int userId) {
         return generateServiceInfo(pkg, s, flags, state, null, userId);
     }
 
     @Nullable
     public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p,
-            @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state,
             @Nullable ApplicationInfo applicationInfo, int userId) {
         if (p == null) return null;
         if (!checkUseInstalled(pkg, state, flags)) {
@@ -634,7 +635,7 @@
      */
     @NonNull
     public static ProviderInfo generateProviderInfoUnchecked(@NonNull ParsedProvider p,
-            @PackageManager.ComponentInfoFlags long flags,
+            @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull ApplicationInfo applicationInfo) {
         // Make shallow copies so we can store the metadata safely
         ProviderInfo pi = new ProviderInfo();
@@ -664,7 +665,7 @@
 
     @Nullable
     public static ProviderInfo generateProviderInfo(ParsingPackageRead pkg, ParsedProvider p,
-            @PackageManager.ComponentInfoFlags long flags, FrameworkPackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, FrameworkPackageUserState state,
             int userId) {
         return generateProviderInfo(pkg, p, flags, state, null, userId);
     }
@@ -675,7 +676,7 @@
      */
     @Nullable
     public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
-            ParsingPackageRead pkg, @PackageManager.ComponentInfoFlags long flags, int userId,
+            ParsingPackageRead pkg, @PackageManager.ComponentInfoFlagsBits long flags, int userId,
             boolean assignUserFields) {
         if (i == null) return null;
 
@@ -706,7 +707,7 @@
 
     @Nullable
     public static PermissionInfo generatePermissionInfo(ParsedPermission p,
-            @PackageManager.ComponentInfoFlags long flags) {
+            @PackageManager.ComponentInfoFlagsBits long flags) {
         if (p == null) return null;
 
         PermissionInfo pi = new PermissionInfo(p.getBackgroundPermission());
@@ -729,7 +730,7 @@
 
     @Nullable
     public static PermissionGroupInfo generatePermissionGroupInfo(ParsedPermissionGroup pg,
-            @PackageManager.ComponentInfoFlags long flags) {
+            @PackageManager.ComponentInfoFlagsBits long flags) {
         if (pg == null) return null;
 
         PermissionGroupInfo pgi = new PermissionGroupInfo(
@@ -887,7 +888,7 @@
     }
 
     private static boolean checkUseInstalled(ParsingPackageRead pkg,
-            FrameworkPackageUserState state, @PackageManager.PackageInfoFlags long flags) {
+            FrameworkPackageUserState state, @PackageManager.PackageInfoFlagsBits long flags) {
         // If available for the target user
         return PackageUserStateUtils.isAvailable(state, flags);
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index 63332e7..fc9f1a5 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageManager.Property;
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedApexSystemService;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedIntentInfo;
@@ -56,6 +57,8 @@
 
     ParsingPackage addAdoptPermission(String adoptPermission);
 
+    ParsingPackage addApexSystemService(ParsedApexSystemService parsedApexSystemService);
+
     ParsingPackage addConfigPreference(ConfigurationInfo configPreference);
 
     ParsingPackage addFeatureGroup(FeatureGroupInfo featureGroup);
@@ -115,6 +118,7 @@
 
     ParsingPackage addQueriesProvider(String authority);
 
+    /** Sets a process name -> {@link ParsedProcess} map coming from the <processes> tag. */
     ParsingPackage setProcesses(@NonNull Map<String, ParsedProcess> processes);
 
     ParsingPackage asSplit(
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 19a8ce9..dbd3d5c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -35,6 +35,8 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedActivityImpl;
+import android.content.pm.parsing.component.ParsedApexSystemService;
+import android.content.pm.parsing.component.ParsedApexSystemServiceImpl;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedAttributionImpl;
 import android.content.pm.parsing.component.ParsedComponent;
@@ -60,6 +62,7 @@
 import android.os.Parcelable;
 import android.os.storage.StorageManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -268,6 +271,9 @@
     protected List<ParsedActivity> activities = emptyList();
 
     @NonNull
+    protected List<ParsedApexSystemService> apexSystemServices = emptyList();
+
+    @NonNull
     protected List<ParsedActivity> receivers = emptyList();
 
     @NonNull
@@ -292,6 +298,9 @@
 //    @DataClass.ParcelWith(ParsingUtils.StringPairListParceler.class)
     private List<Pair<String, ParsedIntentInfo>> preferredActivityFilters = emptyList();
 
+    /**
+     * Map from a process name to a {@link ParsedProcess}.
+     */
     @NonNull
     private Map<String, ParsedProcess> processes = emptyMap();
 
@@ -764,6 +773,14 @@
     }
 
     @Override
+    public final ParsingPackageImpl addApexSystemService(
+            ParsedApexSystemService parsedApexSystemService) {
+        this.apexSystemServices = CollectionUtils.add(
+                this.apexSystemServices, parsedApexSystemService);
+        return this;
+    }
+
+    @Override
     public ParsingPackageImpl addReceiver(ParsedActivity parsedReceiver) {
         this.receivers = CollectionUtils.add(this.receivers, parsedReceiver);
         addMimeGroupsFromComponent(parsedReceiver);
@@ -832,7 +849,6 @@
         return this;
     }
 
-
     @Override public ParsingPackageImpl removeUsesOptionalNativeLibrary(String libraryName) {
         this.usesOptionalNativeLibraries = CollectionUtils.remove(this.usesOptionalNativeLibraries,
                 libraryName);
@@ -1119,10 +1135,46 @@
         appInfo.setSplitCodePaths(splitCodePaths);
         appInfo.setSplitResourcePaths(splitCodePaths);
         appInfo.setVersionCode(mLongVersionCode);
+        appInfo.setAppClassNamesByProcess(buildAppClassNamesByProcess());
 
         return appInfo;
     }
 
+    /**
+     * Create a map from a process name to the custom application class for this process,
+     * which comes from <processes><process android:name="xxx">.
+     *
+     * The original information is stored in {@link #processes}, but it's stored in
+     * a form of: [process name] -[1:N]-> [package name] -[1:N]-> [class name].
+     * We scan it and collect the process names and their app class names, only for this package.
+     *
+     * The resulting map only contains processes with a custom application class set.
+     */
+    @Nullable
+    private ArrayMap<String, String> buildAppClassNamesByProcess() {
+        if (processes == null) {
+            return null;
+        }
+        final ArrayMap<String, String> ret = new ArrayMap<>(4);
+        for (String processName : processes.keySet()) {
+            final ParsedProcess process = processes.get(processName);
+            final ArrayMap<String, String> appClassesByPackage =
+                    process.getAppClassNamesByPackage();
+
+            for (int i = 0; i < appClassesByPackage.size(); i++) {
+                final String packageName = appClassesByPackage.keyAt(i);
+
+                if (this.packageName.equals(packageName)) {
+                    final String appClassName = appClassesByPackage.valueAt(i);
+                    if (!TextUtils.isEmpty(appClassName)) {
+                        ret.put(processName, appClassName);
+                    }
+                }
+            }
+        }
+        return ret;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -1198,6 +1250,7 @@
         ParsingPackageUtils.writeKeySetMapping(dest, this.keySetMapping);
         sForInternedStringList.parcel(this.protectedBroadcasts, dest, flags);
         dest.writeTypedList(this.activities);
+        dest.writeTypedList(this.apexSystemServices);
         dest.writeTypedList(this.receivers);
         dest.writeTypedList(this.services);
         dest.writeTypedList(this.providers);
@@ -1339,6 +1392,8 @@
         this.protectedBroadcasts = sForInternedStringList.unparcel(in);
 
         this.activities = ParsingUtils.createTypedInterfaceList(in, ParsedActivityImpl.CREATOR);
+        this.apexSystemServices = ParsingUtils.createTypedInterfaceList(in,
+                ParsedApexSystemServiceImpl.CREATOR);
         this.receivers = ParsingUtils.createTypedInterfaceList(in, ParsedActivityImpl.CREATOR);
         this.services = ParsingUtils.createTypedInterfaceList(in, ParsedServiceImpl.CREATOR);
         this.providers = ParsingUtils.createTypedInterfaceList(in, ParsedProviderImpl.CREATOR);
@@ -1353,7 +1408,7 @@
         this.processes = in.readHashMap(boot);
         this.metaData = in.readBundle(boot);
         this.volumeUuid = sForInternedString.unparcel(in);
-        this.signingDetails = in.readParcelable(boot);
+        this.signingDetails = in.readParcelable(boot, android.content.pm.SigningDetails.class);
         this.mPath = in.readString();
         this.queriesIntents = in.createTypedArrayList(Intent.CREATOR);
         this.queriesPackages = sForInternedStringList.unparcel(in);
@@ -1706,6 +1761,12 @@
 
     @NonNull
     @Override
+    public List<ParsedApexSystemService> getApexSystemServices() {
+        return apexSystemServices;
+    }
+
+    @NonNull
+    @Override
     public List<ParsedActivity> getReceivers() {
         return receivers;
     }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 49b3b08..c8113ef 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager.Property;
 import android.content.pm.PackageParser;
 import android.content.pm.SigningDetails;
+import android.content.pm.parsing.component.ParsedApexSystemService;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedIntentInfo;
 import android.content.pm.parsing.component.ParsedPermissionGroup;
@@ -55,6 +56,12 @@
     @NonNull
     List<String> getAdoptPermissions();
 
+    /**
+     * @see R.styleable#AndroidManifestApexSystemService
+     */
+    @NonNull
+    List<ParsedApexSystemService> getApexSystemServices();
+
     @NonNull
     List<ParsedAttribution> getAttributions();
 
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 3e537c8..16deaa0 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -53,6 +53,8 @@
 import android.content.pm.parsing.component.ComponentParseUtils;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedActivityUtils;
+import android.content.pm.parsing.component.ParsedApexSystemService;
+import android.content.pm.parsing.component.ParsedApexSystemServiceUtils;
 import android.content.pm.parsing.component.ParsedAttribution;
 import android.content.pm.parsing.component.ParsedAttributionUtils;
 import android.content.pm.parsing.component.ParsedComponent;
@@ -2205,6 +2207,18 @@
 
                     result = activityResult;
                     break;
+                case "apex-system-service":
+                    ParseResult<ParsedApexSystemService> systemServiceResult =
+                            ParsedApexSystemServiceUtils.parseApexSystemService(res,
+                                    parser, input);
+                    if (systemServiceResult.isSuccess()) {
+                        ParsedApexSystemService systemService =
+                                systemServiceResult.getResult();
+                        pkg.addApexSystemService(systemService);
+                    }
+
+                    result = systemServiceResult;
+                    break;
                 default:
                     result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
                     break;
@@ -3497,7 +3511,7 @@
 
             ArraySet<PublicKey> keys = new ArraySet<>(M);
             for (int j = 0; j < M; ++j) {
-                PublicKey pk = (PublicKey) in.readSerializable();
+                PublicKey pk = (PublicKey) in.readSerializable(java.security.PublicKey.class.getClassLoader(), java.security.PublicKey.class);
                 keys.add(pk);
             }
 
diff --git a/core/java/android/content/pm/parsing/ParsingUtils.java b/core/java/android/content/pm/parsing/ParsingUtils.java
index cce984e..6dfb268 100644
--- a/core/java/android/content/pm/parsing/ParsingUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingUtils.java
@@ -138,7 +138,7 @@
             final List<Pair<String, ParsedIntentInfo>> list = new ArrayList<>(size);
             for (int i = 0; i < size; ++i) {
                 list.add(Pair.create(source.readString(), source.readParcelable(
-                        ParsedIntentInfoImpl.class.getClassLoader())));
+                        ParsedIntentInfoImpl.class.getClassLoader(), android.content.pm.parsing.component.ParsedIntentInfo.class)));
             }
 
             return list;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
copy to core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
index fbb78c8..fe821e0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemService.java
@@ -14,12 +14,25 @@
  * limitations under the License.
  */
 
-package com.android.systemui.flags;
+package android.content.pm.parsing.component;
 
-/**
- * Class to manage simple DeviceConfig-based feature flags.
- *
- * See {@link Flags} for instructions on defining new flags.
- */
-public interface FeatureFlags extends FlagReader {
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+/** @hide */
+public interface ParsedApexSystemService extends Parcelable {
+
+    @NonNull
+    String getName();
+
+    @Nullable
+    String getJarPath();
+
+    @Nullable
+    String getMinSdkVersion();
+
+    @Nullable
+    String getMaxSdkVersion();
+
 }
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
new file mode 100644
index 0000000..54196fd
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.parsing.component;
+
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DataClass;
+import com.android.internal.util.Parcelling;
+
+/** @hide **/
+@DataClass(genGetters = true, genAidl = false, genSetters = true, genParcelable = true)
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedApexSystemServiceImpl implements ParsedApexSystemService {
+
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
+    @NonNull
+    private String name;
+
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
+    @Nullable
+    private String jarPath;
+
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
+    @Nullable
+    private String minSdkVersion;
+
+    @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedString.class)
+    @Nullable
+    private String maxSdkVersion;
+
+    public ParsedApexSystemServiceImpl() {
+    }
+
+
+    // 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/parsing/component/ParsedApexSystemServiceImpl.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @DataClass.Generated.Member
+    public ParsedApexSystemServiceImpl(
+            @NonNull String name,
+            @Nullable String jarPath,
+            @Nullable String minSdkVersion,
+            @Nullable String maxSdkVersion) {
+        this.name = name;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, name);
+        this.jarPath = jarPath;
+        this.minSdkVersion = minSdkVersion;
+        this.maxSdkVersion = maxSdkVersion;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull String getName() {
+        return name;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getJarPath() {
+        return jarPath;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getMinSdkVersion() {
+        return minSdkVersion;
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getMaxSdkVersion() {
+        return maxSdkVersion;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull ParsedApexSystemServiceImpl setName(@NonNull String value) {
+        name = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, name);
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull ParsedApexSystemServiceImpl setJarPath(@NonNull String value) {
+        jarPath = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull ParsedApexSystemServiceImpl setMinSdkVersion(@NonNull String value) {
+        minSdkVersion = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull ParsedApexSystemServiceImpl setMaxSdkVersion(@NonNull String value) {
+        maxSdkVersion = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<String> sParcellingForName =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForInternedString.class);
+    static {
+        if (sParcellingForName == null) {
+            sParcellingForName = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForInternedString());
+        }
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<String> sParcellingForJarPath =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForInternedString.class);
+    static {
+        if (sParcellingForJarPath == null) {
+            sParcellingForJarPath = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForInternedString());
+        }
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<String> sParcellingForMinSdkVersion =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForInternedString.class);
+    static {
+        if (sParcellingForMinSdkVersion == null) {
+            sParcellingForMinSdkVersion = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForInternedString());
+        }
+    }
+
+    @DataClass.Generated.Member
+    static Parcelling<String> sParcellingForMaxSdkVersion =
+            Parcelling.Cache.get(
+                    Parcelling.BuiltIn.ForInternedString.class);
+    static {
+        if (sParcellingForMaxSdkVersion == null) {
+            sParcellingForMaxSdkVersion = Parcelling.Cache.put(
+                    new Parcelling.BuiltIn.ForInternedString());
+        }
+    }
+
+    @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 (jarPath != null) flg |= 0x2;
+        if (minSdkVersion != null) flg |= 0x4;
+        if (maxSdkVersion != null) flg |= 0x8;
+        dest.writeByte(flg);
+        sParcellingForName.parcel(name, dest, flags);
+        sParcellingForJarPath.parcel(jarPath, dest, flags);
+        sParcellingForMinSdkVersion.parcel(minSdkVersion, dest, flags);
+        sParcellingForMaxSdkVersion.parcel(maxSdkVersion, dest, flags);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected ParsedApexSystemServiceImpl(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String _name = sParcellingForName.unparcel(in);
+        String _jarPath = sParcellingForJarPath.unparcel(in);
+        String _minSdkVersion = sParcellingForMinSdkVersion.unparcel(in);
+        String _maxSdkVersion = sParcellingForMaxSdkVersion.unparcel(in);
+
+        this.name = _name;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, name);
+        this.jarPath = _jarPath;
+        this.minSdkVersion = _minSdkVersion;
+        this.maxSdkVersion = _maxSdkVersion;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull android.os.Parcelable.Creator<ParsedApexSystemServiceImpl> CREATOR
+            = new android.os.Parcelable.Creator<ParsedApexSystemServiceImpl>() {
+        @Override
+        public ParsedApexSystemServiceImpl[] newArray(int size) {
+            return new ParsedApexSystemServiceImpl[size];
+        }
+
+        @Override
+        public ParsedApexSystemServiceImpl createFromParcel(@NonNull android.os.Parcel in) {
+            return new ParsedApexSystemServiceImpl(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1638903241144L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceImpl.java",
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedApexSystemService]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
new file mode 100644
index 0000000..26abf48
--- /dev/null
+++ b/core/java/android/content/pm/parsing/component/ParsedApexSystemServiceUtils.java
@@ -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 android.content.pm.parsing.component;
+
+import android.R;
+import android.annotation.NonNull;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/** @hide */
+public class ParsedApexSystemServiceUtils {
+
+    @NonNull
+    public static ParseResult<ParsedApexSystemService> parseApexSystemService(
+            Resources res, XmlResourceParser parser, ParseInput input)
+            throws XmlPullParserException, IOException {
+        final ParsedApexSystemServiceImpl systemService =
+                new ParsedApexSystemServiceImpl();
+        TypedArray sa = res.obtainAttributes(parser,
+                R.styleable.AndroidManifestApexSystemService);
+        try {
+            String className = sa.getString(
+                    R.styleable.AndroidManifestApexSystemService_name);
+            if (TextUtils.isEmpty(className)) {
+                return input.error("<apex-system-service> does not have name attribute");
+            }
+
+            String jarPath = sa.getString(
+                    R.styleable.AndroidManifestApexSystemService_path);
+            String minSdkVersion = sa.getString(
+                    R.styleable.AndroidManifestApexSystemService_minSdkVersion);
+            String maxSdkVersion = sa.getString(
+                    R.styleable.AndroidManifestApexSystemService_maxSdkVersion);
+
+            systemService.setName(className)
+                    .setMinSdkVersion(minSdkVersion)
+                    .setMaxSdkVersion(maxSdkVersion);
+            if (!TextUtils.isEmpty(jarPath)) {
+                systemService.setJarPath(jarPath);
+            }
+
+            return input.success(systemService);
+        } finally {
+            sa.recycle();
+        }
+    }
+}
diff --git a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java b/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
index 2145e44..45038cf 100644
--- a/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
@@ -116,7 +116,7 @@
         this.requestRes = in.readInt();
         this.protectionLevel = in.readInt();
         this.tree = in.readBoolean();
-        this.parsedPermissionGroup = in.readParcelable(boot);
+        this.parsedPermissionGroup = in.readParcelable(boot, android.content.pm.parsing.component.ParsedPermissionGroup.class);
         this.knownCerts = sForStringSet.unparcel(in);
     }
 
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcess.java b/core/java/android/content/pm/parsing/component/ParsedProcess.java
index c2d5163..27a540d 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcess.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcess.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.pm.ApplicationInfo;
 import android.os.Parcelable;
+import android.util.ArrayMap;
 
 import java.util.Set;
 
@@ -37,6 +38,15 @@
     @NonNull
     String getName();
 
+    /**
+     * The app class names in this (potentially shared) process, from a package name to
+     * the application class name.
+     * It's a map, because in shared processes, different packages can have different application
+     * classes.
+     */
+    @NonNull
+    ArrayMap<String, String> getAppClassNamesByPackage();
+
     @ApplicationInfo.NativeHeapZeroInitialized
     int getNativeHeapZeroInitialized();
 }
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
index 3fd60eb..d404ecfd 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
@@ -22,6 +22,7 @@
 import android.content.pm.ApplicationInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -39,6 +40,11 @@
 
     @NonNull
     private String name;
+
+    /** @see ParsedProcess#getAppClassNamesByPackage() */
+    @NonNull
+    private ArrayMap<String, String> appClassNamesByPackage = ArrayMap.EMPTY;
+
     @NonNull
     @DataClass.ParcelWith(Parcelling.BuiltIn.ForInternedStringSet.class)
     private Set<String> deniedPermissions = emptySet();
@@ -55,6 +61,8 @@
 
     public ParsedProcessImpl(@NonNull ParsedProcess other) {
         name = other.getName();
+        appClassNamesByPackage = (other.getAppClassNamesByPackage().size() == 0)
+                ? ArrayMap.EMPTY : new ArrayMap<>(other.getAppClassNamesByPackage());
         deniedPermissions = new ArraySet<>(other.getDeniedPermissions());
         gwpAsanMode = other.getGwpAsanMode();
         memtagMode = other.getMemtagMode();
@@ -66,6 +74,21 @@
         gwpAsanMode = other.getGwpAsanMode();
         memtagMode = other.getMemtagMode();
         nativeHeapZeroInitialized = other.getNativeHeapZeroInitialized();
+
+        final ArrayMap<String, String> oacn = other.getAppClassNamesByPackage();
+        for (int i = 0; i < oacn.size(); i++) {
+            appClassNamesByPackage.put(oacn.keyAt(i), oacn.valueAt(i));
+        }
+    }
+
+    /**
+     * Sets a custom application name used in this process for a given package.
+     */
+    public void putAppClassNameForPackage(String packageName, String className) {
+        if (appClassNamesByPackage.size() == 0) {
+            appClassNamesByPackage = new ArrayMap<>(4);
+        }
+        appClassNamesByPackage.put(packageName, className);
     }
 
 
@@ -83,9 +106,14 @@
     //@formatter:off
 
 
+    /**
+     * Creates a new ParsedProcessImpl.
+     *
+     */
     @DataClass.Generated.Member
     public ParsedProcessImpl(
             @NonNull String name,
+            @NonNull ArrayMap<String,String> appClassNamesByPackage,
             @NonNull Set<String> deniedPermissions,
             @ApplicationInfo.GwpAsanMode int gwpAsanMode,
             @ApplicationInfo.MemtagMode int memtagMode,
@@ -93,6 +121,9 @@
         this.name = name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
+        this.appClassNamesByPackage = appClassNamesByPackage;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, appClassNamesByPackage);
         this.deniedPermissions = deniedPermissions;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, deniedPermissions);
@@ -114,6 +145,14 @@
         return name;
     }
 
+    /**
+     * @see ParsedProcess#getAppClassNamesByPackage()
+     */
+    @DataClass.Generated.Member
+    public @NonNull ArrayMap<String,String> getAppClassNamesByPackage() {
+        return appClassNamesByPackage;
+    }
+
     @DataClass.Generated.Member
     public @NonNull Set<String> getDeniedPermissions() {
         return deniedPermissions;
@@ -142,6 +181,17 @@
         return this;
     }
 
+    /**
+     * @see ParsedProcess#getAppClassNamesByPackage()
+     */
+    @DataClass.Generated.Member
+    public @NonNull ParsedProcessImpl setAppClassNamesByPackage(@NonNull ArrayMap<String,String> value) {
+        appClassNamesByPackage = value;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, appClassNamesByPackage);
+        return this;
+    }
+
     @DataClass.Generated.Member
     public @NonNull ParsedProcessImpl setDeniedPermissions(@NonNull Set<String> value) {
         deniedPermissions = value;
@@ -192,6 +242,7 @@
         // void parcelFieldName(Parcel dest, int flags) { ... }
 
         dest.writeString(name);
+        dest.writeMap(appClassNamesByPackage);
         sParcellingForDeniedPermissions.parcel(deniedPermissions, dest, flags);
         dest.writeInt(gwpAsanMode);
         dest.writeInt(memtagMode);
@@ -210,6 +261,8 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         String _name = in.readString();
+        ArrayMap<String,String> _appClassNamesByPackage = new ArrayMap();
+        in.readMap(_appClassNamesByPackage, String.class.getClassLoader());
         Set<String> _deniedPermissions = sParcellingForDeniedPermissions.unparcel(in);
         int _gwpAsanMode = in.readInt();
         int _memtagMode = in.readInt();
@@ -218,6 +271,9 @@
         this.name = _name;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, name);
+        this.appClassNamesByPackage = _appClassNamesByPackage;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, appClassNamesByPackage);
         this.deniedPermissions = _deniedPermissions;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, deniedPermissions);
@@ -249,10 +305,10 @@
     };
 
     @DataClass.Generated(
-            time = 1627605368434L,
+            time = 1639076603310L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
index cf83586..5e4cf66 100644
--- a/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedProcessUtils.java
@@ -97,7 +97,12 @@
                 return input.error(processNameResult);
             }
 
+            String packageName = pkg.getPackageName();
+            String className = ParsingUtils.buildClassName(packageName,
+                    sa.getNonConfigurationString(R.styleable.AndroidManifestProcess_name, 0));
+
             proc.setName(processNameResult.getResult());
+            proc.putAppClassNameForPackage(packageName, className);
             proc.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestProcess_gwpAsanMode, -1));
             proc.setMemtagMode(sa.getInt(R.styleable.AndroidManifestProcess_memtagMode, -1));
             if (sa.hasValue(R.styleable.AndroidManifestProcess_nativeHeapZeroInitialized)) {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 24c6a5a..bfd9fd0 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -792,6 +792,21 @@
     }
 
     /**
+     * To get the parent theme resource id according to the parameter theme resource id.
+     * @param resId theme resource id.
+     * @return the parent theme resource id.
+     * @hide
+     */
+    @StyleRes
+    int getParentThemeIdentifier(@StyleRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            // name is checked in JNI.
+            return nativeGetParentThemeIdentifier(mObject, resId);
+        }
+    }
+
+    /**
      * Enable resource resolution logging to track the steps taken to resolve the last resource
      * entry retrieved. Stores the configuration and package names for each step.
      *
@@ -1600,6 +1615,8 @@
     private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
             String prefix);
     static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
+    @StyleRes
+    private static native int nativeGetParentThemeIdentifier(long ptr, @StyleRes int styleId);
 
     // AssetInputStream related native methods.
     private static native void nativeAssetDestroy(long assetPtr);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index a6f2e40..cb53a2a 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -80,6 +80,7 @@
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.function.Consumer;
@@ -1507,6 +1508,12 @@
      * retrieve XML attributes with style and theme information applied.
      */
     public final class Theme {
+        /**
+         * To trace parent themes needs to prevent a cycle situation.
+         * e.x. A's parent is B, B's parent is C, and C's parent is A.
+         */
+        private static final int MAX_NUMBER_OF_TRACING_PARENT_THEME = 100;
+
         private final Object mLock = new Object();
 
         @GuardedBy("mLock")
@@ -1800,6 +1807,13 @@
             }
         }
 
+        @StyleRes
+        /*package*/ int getParentThemeIdentifier(@StyleRes int resId) {
+            synchronized (mLock) {
+                return mThemeImpl.getParentThemeIdentifier(resId);
+            }
+        }
+
         /**
          * @hide
          */
@@ -1923,6 +1937,54 @@
                 }
             }
         }
+
+        @Override
+        public int hashCode() {
+            return getKey().hashCode();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+
+            if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) {
+                return false;
+            }
+
+            final Theme other = (Theme) o;
+            return getKey().equals(other.getKey());
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append('{');
+            int themeResId = getAppliedStyleResId();
+            int i = 0;
+            sb.append("InheritanceMap=[");
+            while (themeResId > 0) {
+                if (i > MAX_NUMBER_OF_TRACING_PARENT_THEME) {
+                    sb.append(",...");
+                    break;
+                }
+
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                sb.append("id=0x").append(Integer.toHexString(themeResId));
+                sb.append(getResourcePackageName(themeResId))
+                        .append(":").append(getResourceTypeName(themeResId))
+                        .append("/").append(getResourceEntryName(themeResId));
+
+                i++;
+                themeResId = getParentThemeIdentifier(themeResId);
+            }
+            sb.append("], Themes=").append(Arrays.deepToString(getTheme()));
+            sb.append('}');
+            return sb.toString();
+        }
     }
 
     static class ThemeKey implements Cloneable {
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index b9f93b8..4d850b0c 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1310,6 +1310,14 @@
             return mThemeResId;
         }
 
+        @StyleRes
+        /*package*/ int getParentThemeIdentifier(@StyleRes int resId) {
+            if (resId > 0) {
+                return mAssets.getParentThemeIdentifier(resId);
+            }
+            return 0;
+        }
+
         void applyStyle(int resId, boolean force) {
             mAssets.applyStyleToTheme(mTheme, resId, force);
             mThemeResId = resId;
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 5bc235f..6c07356 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -16,8 +16,9 @@
 
 package android.content.res;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -238,7 +239,10 @@
 
 
             if (type == ids.boldId) {
-                buffer.setSpan(new StyleSpan(Typeface.BOLD),
+                Application application = ActivityThread.currentApplication();
+                int fontWeightAdjustment =
+                        application.getResources().getConfiguration().fontWeightAdjustment;
+                buffer.setSpan(new StyleSpan(Typeface.BOLD, fontWeightAdjustment),
                                style[i+1], style[i+2]+1,
                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
             } else if (type == ids.italicId) {
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index d6f55d6..9eb9cd5 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -527,11 +527,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
-                + " to color: type=0x" + Integer.toHexString(type));
+                + " to color: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
     }
 
     /**
@@ -561,7 +562,8 @@
         if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
-                        "Failed to resolve attribute at index " + index + ": " + value);
+                        "Failed to resolve attribute at index " + index + ": " + value
+                                + ", theme=" + mTheme);
             }
             return mResources.loadComplexColor(value, value.resourceId, mTheme);
         }
@@ -596,7 +598,8 @@
         if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
-                        "Failed to resolve attribute at index " + index + ": " + value);
+                        "Failed to resolve attribute at index " + index + ": " + value
+                                + ", theme=" + mTheme);
             }
             return mResources.loadColorStateList(value, value.resourceId, mTheme);
         }
@@ -637,11 +640,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
-                + " to integer: type=0x" + Integer.toHexString(type));
+                + " to integer: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
     }
 
     /**
@@ -684,11 +688,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
-                + " to dimension: type=0x" + Integer.toHexString(type));
+                + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
     }
 
     /**
@@ -732,11 +737,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
-                + " to dimension: type=0x" + Integer.toHexString(type));
+                + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
     }
 
     /**
@@ -781,11 +787,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
-                + " to dimension: type=0x" + Integer.toHexString(type));
+                + " to dimension: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
     }
 
     /**
@@ -825,11 +832,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException(getPositionDescription()
-                + ": You must supply a " + name + " attribute.");
+                + ": You must supply a " + name + " attribute." + ", theme=" + mTheme);
     }
 
     /**
@@ -900,11 +908,12 @@
             final TypedValue value = mValue;
             getValueAt(index, value);
             throw new UnsupportedOperationException(
-                    "Failed to resolve attribute at index " + attrIndex + ": " + value);
+                    "Failed to resolve attribute at index " + attrIndex + ": " + value
+                            + ", theme=" + mTheme);
         }
 
         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
-                + " to fraction: type=0x" + Integer.toHexString(type));
+                + " to fraction: type=0x" + Integer.toHexString(type) + ", theme=" + mTheme);
     }
 
     /**
@@ -996,7 +1005,8 @@
         if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
-                        "Failed to resolve attribute at index " + index + ": " + value);
+                        "Failed to resolve attribute at index " + index + ": " + value
+                                + ", theme=" + mTheme);
             }
 
             if (density > 0) {
@@ -1032,7 +1042,8 @@
         if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
-                        "Failed to resolve attribute at index " + index + ": " + value);
+                        "Failed to resolve attribute at index " + index + ": " + value
+                                + ", theme=" + mTheme);
             }
             return mResources.getFont(value, value.resourceId);
         }
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 1db948a..ccb7cf1 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -143,7 +143,6 @@
             throw new AssertionError(); // Not possible, the native code won't return it.
         }
         mCloseGuard.open("close");
-        recordNewWindow(Binder.getCallingPid(), mWindowPtr);
     }
 
     /**
@@ -191,7 +190,6 @@
             mCloseGuard.close();
         }
         if (mWindowPtr != 0) {
-            recordClosingOfWindow(mWindowPtr);
             nativeDispose(mWindowPtr);
             mWindowPtr = 0;
         }
@@ -746,64 +744,6 @@
         dispose();
     }
 
-    @UnsupportedAppUsage
-    private static final LongSparseArray<Integer> sWindowToPidMap = new LongSparseArray<Integer>();
-
-    private void recordNewWindow(int pid, long window) {
-        synchronized (sWindowToPidMap) {
-            sWindowToPidMap.put(window, pid);
-            if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
-                Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
-            }
-        }
-    }
-
-    private void recordClosingOfWindow(long window) {
-        synchronized (sWindowToPidMap) {
-            if (sWindowToPidMap.size() == 0) {
-                // this means we are not in the ContentProvider.
-                return;
-            }
-            sWindowToPidMap.delete(window);
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private String printStats() {
-        StringBuilder buff = new StringBuilder();
-        int myPid = Process.myPid();
-        int total = 0;
-        SparseIntArray pidCounts = new SparseIntArray();
-        synchronized (sWindowToPidMap) {
-            int size = sWindowToPidMap.size();
-            if (size == 0) {
-                // this means we are not in the ContentProvider.
-                return "";
-            }
-            for (int indx = 0; indx < size; indx++) {
-                int pid = sWindowToPidMap.valueAt(indx);
-                int value = pidCounts.get(pid);
-                pidCounts.put(pid, ++value);
-            }
-        }
-        int numPids = pidCounts.size();
-        for (int i = 0; i < numPids;i++) {
-            buff.append(" (# cursors opened by ");
-            int pid = pidCounts.keyAt(i);
-            if (pid == myPid) {
-                buff.append("this proc=");
-            } else {
-                buff.append("pid ").append(pid).append('=');
-            }
-            int num = pidCounts.get(pid);
-            buff.append(num).append(')');
-            total += num;
-        }
-        // limit the returned string size to 1000
-        String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
-        return "# Open Cursors=" + total + s;
-    }
-
     private static int getCursorWindowSize() {
         if (sCursorWindowSize < 0) {
             // The cursor window size. resource xml file specifies the value in kB.
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 7e61b48..328858b 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -26,14 +26,13 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.LruCache;
 import android.util.Pair;
 import android.util.Printer;
-
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
-
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.FileSystems;
@@ -177,7 +176,7 @@
         mConfiguration = new SQLiteDatabaseConfiguration(configuration);
         mConnectionId = connectionId;
         mIsPrimaryConnection = primaryConnection;
-        mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
+        mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase();
         mPreparedStatementCache = new PreparedStatementCache(
                 mConfiguration.maxSqlCacheSize);
         mCloseGuard.open("close");
@@ -266,7 +265,8 @@
         }
         setPageSize();
         setForeignKeyModeFromConfiguration();
-        setWalModeFromConfiguration();
+        setJournalFromConfiguration();
+        setSyncModeFromConfiguration();
         setJournalSizeLimit();
         setAutoCheckpointInterval();
         setLocaleFromConfiguration();
@@ -334,30 +334,19 @@
         }
     }
 
-    private void setWalModeFromConfiguration() {
-        if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
-            final boolean walEnabled =
-                    (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
-            // Use compatibility WAL unless an app explicitly set journal/synchronous mode
-            // or DISABLE_COMPATIBILITY_WAL flag is set
-            final boolean isCompatibilityWalEnabled =
-                    mConfiguration.isLegacyCompatibilityWalEnabled();
-            if (walEnabled || isCompatibilityWalEnabled) {
-                setJournalMode("WAL");
-                if (mConfiguration.syncMode != null) {
-                    setSyncMode(mConfiguration.syncMode);
-                } else if (isCompatibilityWalEnabled) {
-                    setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
-                } else {
-                    setSyncMode(SQLiteGlobal.getWALSyncMode());
-                }
-                maybeTruncateWalFile();
-            } else {
-                setJournalMode(mConfiguration.journalMode == null
-                        ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
-                setSyncMode(mConfiguration.syncMode == null
-                        ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
-            }
+    private void setJournalFromConfiguration() {
+        if (!mIsReadOnlyConnection) {
+            setJournalMode(mConfiguration.resolveJournalMode());
+            maybeTruncateWalFile();
+        } else {
+            // No need to truncate for read only databases.
+            mConfiguration.shouldTruncateWalFile = false;
+        }
+    }
+
+    private void setSyncModeFromConfiguration() {
+        if (!mIsReadOnlyConnection) {
+            setSyncMode(mConfiguration.resolveSyncMode());
         }
     }
 
@@ -366,6 +355,10 @@
      * PRAGMA wal_checkpoint.
      */
     private void maybeTruncateWalFile() {
+        if (!mConfiguration.shouldTruncateWalFile) {
+            return;
+        }
+
         final long threshold = SQLiteGlobal.getWALTruncateSize();
         if (DEBUG) {
             Log.d(TAG, "Truncate threshold=" + threshold);
@@ -390,12 +383,17 @@
                 + threshold + "; truncating");
         try {
             executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
+            mConfiguration.shouldTruncateWalFile = false;
         } catch (SQLiteException e) {
             Log.w(TAG, "Failed to truncate the -wal file", e);
         }
     }
 
-    private void setSyncMode(String newValue) {
+    private void setSyncMode(@SQLiteDatabase.SyncMode String newValue) {
+        if (TextUtils.isEmpty(newValue)) {
+            // No change to the sync mode is intended
+            return;
+        }
         String value = executeForString("PRAGMA synchronous", null, null);
         if (!canonicalizeSyncMode(value).equalsIgnoreCase(
                 canonicalizeSyncMode(newValue))) {
@@ -403,16 +401,21 @@
         }
     }
 
-    private static String canonicalizeSyncMode(String value) {
+    private static @SQLiteDatabase.SyncMode String canonicalizeSyncMode(String value) {
         switch (value) {
-            case "0": return "OFF";
-            case "1": return "NORMAL";
-            case "2": return "FULL";
+            case "0": return SQLiteDatabase.SYNC_MODE_OFF;
+            case "1": return SQLiteDatabase.SYNC_MODE_NORMAL;
+            case "2": return SQLiteDatabase.SYNC_MODE_FULL;
+            case "3": return SQLiteDatabase.SYNC_MODE_EXTRA;
         }
         return value;
     }
 
-    private void setJournalMode(String newValue) {
+    private void setJournalMode(@SQLiteDatabase.JournalMode String newValue) {
+        if (TextUtils.isEmpty(newValue)) {
+            // No change to the journal mode is intended
+            return;
+        }
         String value = executeForString("PRAGMA journal_mode", null, null);
         if (!value.equalsIgnoreCase(newValue)) {
             try {
@@ -565,9 +568,6 @@
         // Remember what changed.
         boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
                 != mConfiguration.foreignKeyConstraintsEnabled;
-        boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
-                & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
-                | SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL)) != 0;
         boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
         boolean customScalarFunctionsChanged = !configuration.customScalarFunctions
                 .equals(mConfiguration.customScalarFunctions);
@@ -586,9 +586,19 @@
         if (foreignKeyModeChanged) {
             setForeignKeyModeFromConfiguration();
         }
-        if (walModeChanged) {
-            setWalModeFromConfiguration();
+
+        boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
+                mConfiguration.resolveJournalMode());
+        if (journalModeChanged) {
+            setJournalFromConfiguration();
         }
+
+        boolean syncModeChanged =
+                !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
+        if (syncModeChanged) {
+            setSyncModeFromConfiguration();
+        }
+
         if (localeChanged) {
             setLocaleFromConfiguration();
         }
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 852f8f2..d3ad6bb 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -106,7 +106,11 @@
     @GuardedBy("mLock")
     private IdleConnectionHandler mIdleConnectionHandler;
 
-    private final AtomicLong mTotalExecutionTimeCounter = new AtomicLong(0);
+    // whole execution time for this connection in milliseconds.
+    private final AtomicLong mTotalStatementsTime = new AtomicLong(0);
+
+    // total statements executed by this connection
+    private final AtomicLong mTotalStatementsCount = new AtomicLong(0);
 
     // Describes what should happen to an acquired connection when it is returned to the pool.
     enum AcquiredConnectionStatus {
@@ -286,8 +290,11 @@
         synchronized (mLock) {
             throwIfClosedLocked();
 
-            boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
-                    & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+            boolean isWalCurrentMode = mConfiguration.resolveJournalMode().equalsIgnoreCase(
+                    SQLiteDatabase.JOURNAL_MODE_WAL);
+            boolean isWalNewMode = configuration.resolveJournalMode().equalsIgnoreCase(
+                    SQLiteDatabase.JOURNAL_MODE_WAL);
+            boolean walModeChanged = isWalCurrentMode ^ isWalNewMode;
             if (walModeChanged) {
                 // WAL mode can only be changed if there are no acquired connections
                 // because we need to close all but the primary connection first.
@@ -536,7 +543,8 @@
     }
 
     void onStatementExecuted(long executionTimeMs) {
-        mTotalExecutionTimeCounter.addAndGet(executionTimeMs);
+        mTotalStatementsTime.addAndGet(executionTimeMs);
+        mTotalStatementsCount.incrementAndGet();
     }
 
     // Can't throw.
@@ -1037,8 +1045,7 @@
     }
 
     private void setMaxConnectionPoolSizeLocked() {
-        if (!mConfiguration.isInMemoryDb()
-                && (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+        if (mConfiguration.resolveJournalMode().equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_WAL)) {
             mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
         } else {
             // We don't actually need to always restrict the connection pool size to 1
@@ -1117,11 +1124,18 @@
             printer.println("Connection pool for " + mConfiguration.path + ":");
             printer.println("  Open: " + mIsOpen);
             printer.println("  Max connections: " + mMaxConnectionPoolSize);
-            printer.println("  Total execution time: " + mTotalExecutionTimeCounter);
+            printer.println("  Total execution time (ms): " + mTotalStatementsTime);
+            printer.println("  Total statements executed: " + mTotalStatementsCount);
+            if (mTotalStatementsCount.get() > 0) {
+                // Avoid division by 0 by filtering out logs where there are no statements executed.
+                printer.println("  Average time per statement (ms): "
+                        + mTotalStatementsTime.get() / mTotalStatementsCount.get());
+            }
             printer.println("  Configuration: openFlags=" + mConfiguration.openFlags
                     + ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
-                    + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.journalMode)
-                    + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.syncMode));
+                    + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+                    + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
+            printer.println("  IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
 
             if (isCompatibilityWalEnabled) {
                 printer.println("  Compatibility WAL enabled: wal_syncmode="
@@ -1182,6 +1196,14 @@
         }
     }
 
+    public long getTotalStatementsTime() {
+        return mTotalStatementsTime.get();
+    }
+
+    public long getTotalStatementsCount() {
+        return mTotalStatementsCount.get();
+    }
+
     @Override
     public String toString() {
         return "SQLiteConnectionPool: " + mConfiguration.path;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 9684ece..0d0615a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -20,6 +20,8 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -42,11 +44,8 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Printer;
-
 import com.android.internal.util.Preconditions;
-
 import dalvik.system.CloseGuard;
-
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
@@ -290,15 +289,182 @@
      */
     public static final int MAX_SQL_CACHE_SIZE = 100;
 
-    private SQLiteDatabase(final String path, final int openFlags,
-            CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
+    /**
+     * @hide
+     */
+    @StringDef(prefix = {"JOURNAL_MODE_"},
+            value =
+                    {
+                            JOURNAL_MODE_WAL,
+                            JOURNAL_MODE_PERSIST,
+                            JOURNAL_MODE_TRUNCATE,
+                            JOURNAL_MODE_MEMORY,
+                            JOURNAL_MODE_DELETE,
+                            JOURNAL_MODE_OFF,
+                    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface JournalMode {}
+
+    /**
+     * The {@code WAL} journaling mode uses a write-ahead log instead of a rollback journal to
+     * implement transactions. The WAL journaling mode is persistent; after being set it stays
+     * in effect across multiple database connections and after closing and reopening the database.
+     *
+     * Performance Considerations:
+     * This mode is recommended when the goal is to improve write performance or parallel read/write
+     * performance. However, it is important to note that WAL introduces checkpoints which commit
+     * all transactions that have not been synced to the database thus to maximize read performance
+     * and lower checkpointing cost a small journal size is recommended. However, other modes such
+     * as {@code DELETE} will not perform checkpoints, so it is a trade off that needs to be
+     * considered as part of the decision of which journal mode to use.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+     * details.</p>
+     */
+    public static final String JOURNAL_MODE_WAL = "WAL";
+
+    /**
+     * The {@code PERSIST} journaling mode prevents the rollback journal from being deleted at the
+     * end of each transaction. Instead, the header of the journal is overwritten with zeros.
+     * This will prevent other database connections from rolling the journal back.
+     *
+     * This mode is useful as an optimization on platforms where deleting or truncating a file is
+     * much more expensive than overwriting the first block of a file with zeros.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+     * details.</p>
+     */
+    public static final String JOURNAL_MODE_PERSIST = "PERSIST";
+
+    /**
+     * The {@code TRUNCATE} journaling mode commits transactions by truncating the rollback journal
+     * to zero-length instead of deleting it. On many systems, truncating a file is much faster than
+     * deleting the file since the containing directory does not need to be changed.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+     * details.</p>
+     */
+    public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
+
+    /**
+     * The {@code MEMORY} journaling mode stores the rollback journal in volatile RAM.
+     * This saves disk I/O but at the expense of database safety and integrity. If the application
+     * using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set,
+     * then the database file will very likely go corrupt.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+     * details.</p>
+     */
+    public static final String JOURNAL_MODE_MEMORY = "MEMORY";
+
+    /**
+     * The {@code DELETE} journaling mode is the normal behavior. In the DELETE mode, the rollback
+     * journal is deleted at the conclusion of each transaction.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+     * details.</p>
+     */
+    public static final String JOURNAL_MODE_DELETE = "DELETE";
+
+    /**
+     * The {@code OFF} journaling mode disables the rollback journal completely. No rollback journal
+     * is ever created and hence there is never a rollback journal to delete. The OFF journaling
+     * mode disables the atomic commit and rollback capabilities of SQLite. The ROLLBACK command
+     * behaves in an undefined way thus applications must avoid using the ROLLBACK command.
+     * If the application crashes in the middle of a transaction, then the database file will very
+     * likely go corrupt.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+     * details.</p>
+     */
+    public static final String JOURNAL_MODE_OFF = "OFF";
+
+    /**
+     * @hide
+     */
+    @StringDef(prefix = {"SYNC_MODE_"},
+            value =
+                    {
+                            SYNC_MODE_EXTRA,
+                            SYNC_MODE_FULL,
+                            SYNC_MODE_NORMAL,
+                            SYNC_MODE_OFF,
+                    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SyncMode {}
+
+    /**
+     * The {@code EXTRA} sync mode is like {@code FULL} sync mode with the addition that the
+     * directory containing a rollback journal is synced after that journal is unlinked to commit a
+     * transaction in {@code DELETE} journal mode.
+     *
+     * {@code EXTRA} provides additional durability if the commit is followed closely by a
+     * power loss.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+     * details.</p>
+     */
+    @SuppressLint("IntentName") public static final String SYNC_MODE_EXTRA = "EXTRA";
+
+    /**
+     * In {@code FULL} sync mode the SQLite database engine will use the xSync method of the VFS
+     * to ensure that all content is safely written to the disk surface prior to continuing.
+     * This ensures that an operating system crash or power failure will not corrupt the database.
+     * {@code FULL} is very safe, but it is also slower.
+     *
+     * {@code FULL} is the most commonly used synchronous setting when not in WAL mode.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+     * details.</p>
+     */
+    public static final String SYNC_MODE_FULL = "FULL";
+
+    /**
+     * The {@code NORMAL} sync mode, the SQLite database engine will still sync at the most critical
+     * moments, but less often than in {@code FULL} mode. There is a very small chance that a
+     * power failure at the wrong time could corrupt the database in {@code DELETE} journal mode on
+     * an older filesystem.
+     *
+     * {@code WAL} journal mode is safe from corruption with {@code NORMAL} sync mode, and probably
+     * {@code DELETE} sync mode is safe too on modern filesystems. WAL mode is always consistent
+     * with {@code NORMAL} sync mode, but WAL mode does lose durability. A transaction committed in
+     * WAL mode with {@code NORMAL} might roll back following a power loss or system crash.
+     * Transactions are durable across application crashes regardless of the synchronous setting
+     * or journal mode.
+     *
+     * The {@code NORMAL} sync mode is a good choice for most applications running in WAL mode.
+     *
+     * <p>Caveat: Even though this sync mode is safe Be careful when using {@code NORMAL} sync mode
+     * when dealing with data dependencies between multiple databases, unless those databases use
+     * the same durability or are somehow synced, there could be corruption.</p>
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+     * details.</p>
+     */
+    public static final String SYNC_MODE_NORMAL = "NORMAL";
+
+    /**
+     * In {@code OFF} sync mode SQLite continues without syncing as soon as it has handed data off
+     * to the operating system. If the application running SQLite crashes, the data will be safe,
+     * but the database might become corrupted if the operating system crashes or the computer loses
+     * power before that data has been written to the disk surface. On the other hand, commits can
+     * be orders of magnitude faster with synchronous {@code OFF}.
+     *
+     * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+     * details.</p>
+     */
+    public static final String SYNC_MODE_OFF = "OFF";
+
+    private SQLiteDatabase(@Nullable final String path, @Nullable final int openFlags,
+            @Nullable CursorFactory cursorFactory, @Nullable DatabaseErrorHandler errorHandler,
             int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
-            String journalMode, String syncMode) {
+            @Nullable String journalMode, @Nullable String syncMode) {
         mCursorFactory = cursorFactory;
         mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
         mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
         mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
         mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
+
         // Disable lookaside allocator on low-RAM devices
         if (ActivityManager.isLowRamDeviceStatic()) {
             mConfigurationLocked.lookasideSlotCount = 0;
@@ -316,11 +482,11 @@
             }
         }
         mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
-        mConfigurationLocked.journalMode = journalMode;
-        mConfigurationLocked.syncMode = syncMode;
         if (SQLiteCompatibilityWalFlags.isLegacyCompatibilityWalEnabled()) {
             mConfigurationLocked.openFlags |= ENABLE_LEGACY_COMPATIBILITY_WAL;
         }
+        mConfigurationLocked.journalMode = journalMode;
+        mConfigurationLocked.syncMode = syncMode;
     }
 
     @Override
@@ -2191,7 +2357,8 @@
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+            if (mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
+                        SQLiteDatabase.JOURNAL_MODE_WAL)) {
                 return true;
             }
 
@@ -2241,11 +2408,9 @@
             throwIfNotOpenLocked();
 
             final int oldFlags = mConfigurationLocked.openFlags;
-            final boolean walEnabled = (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
-            final boolean compatibilityWalEnabled =
-                    (oldFlags & ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
             // WAL was never enabled for this database, so there's nothing left to do.
-            if (!walEnabled && !compatibilityWalEnabled) {
+            if (!mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
+                        SQLiteDatabase.JOURNAL_MODE_WAL)) {
                 return;
             }
 
@@ -2275,7 +2440,8 @@
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
-            return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
+            return mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
+                    SQLiteDatabase.JOURNAL_MODE_WAL);
         }
     }
 
@@ -2309,6 +2475,20 @@
         return databases;
     }
 
+    private static ArrayList<SQLiteConnectionPool> getActiveDatabasePools() {
+        ArrayList<SQLiteConnectionPool> connectionPools = new ArrayList<SQLiteConnectionPool>();
+        synchronized (sActiveDatabases) {
+            for (SQLiteDatabase db : sActiveDatabases.keySet()) {
+                synchronized (db.mLock) {
+                    if (db.mConnectionPoolLocked != null) {
+                        connectionPools.add(db.mConnectionPoolLocked);
+                    }
+                }
+            }
+        }
+        return connectionPools;
+    }
+
     /**
      * Dump detailed information about all open databases in the current process.
      * Used by bug report.
@@ -2317,8 +2497,45 @@
         // Use this ArraySet to collect file paths.
         final ArraySet<String> directories = new ArraySet<>();
 
-        for (SQLiteDatabase db : getActiveDatabases()) {
-            db.dump(printer, verbose, isSystem, directories);
+        // Accounting across all databases
+        long totalStatementsTimeInMs = 0;
+        long totalStatementsCount = 0;
+
+        ArrayList<SQLiteConnectionPool> activeConnectionPools = getActiveDatabasePools();
+
+        activeConnectionPools.sort(
+                (a, b) -> Long.compare(b.getTotalStatementsCount(), a.getTotalStatementsCount()));
+        for (SQLiteConnectionPool dbPool : activeConnectionPools) {
+            dbPool.dump(printer, verbose, directories);
+            totalStatementsTimeInMs += dbPool.getTotalStatementsTime();
+            totalStatementsCount += dbPool.getTotalStatementsCount();
+        }
+
+        if (totalStatementsCount > 0) {
+            // Only print when there is information available
+
+            // Sorted statements per database
+            printer.println("Statements Executed per Database");
+            for (SQLiteConnectionPool dbPool : activeConnectionPools) {
+                printer.println(
+                        "  " + dbPool.getPath() + " :    " + dbPool.getTotalStatementsCount());
+            }
+            printer.println("");
+            printer.println(
+                    "Total Statements Executed for all Active Databases: " + totalStatementsCount);
+
+            // Sorted execution time per database
+            activeConnectionPools.sort(
+                    (a, b) -> Long.compare(b.getTotalStatementsTime(), a.getTotalStatementsTime()));
+            printer.println("");
+            printer.println("");
+            printer.println("Statement Time per Database (ms)");
+            for (SQLiteConnectionPool dbPool : activeConnectionPools) {
+                printer.println(
+                        "  " + dbPool.getPath() + " :    " + dbPool.getTotalStatementsTime());
+            }
+            printer.println("Total Statements Time for all Active Databases (ms): "
+                    + totalStatementsTimeInMs);
         }
 
         // Dump DB files in the directories.
@@ -2331,15 +2548,6 @@
         }
     }
 
-    private void dump(Printer printer, boolean verbose, boolean isSystem, ArraySet directories) {
-        synchronized (mLock) {
-            if (mConnectionPoolLocked != null) {
-                printer.println("");
-                mConnectionPoolLocked.dump(printer, verbose, directories);
-            }
-        }
-    }
-
     private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
         pw.println("");
         pw.println("Database files in " + dir.getAbsolutePath() + ":");
@@ -2598,9 +2806,7 @@
 
         /**
          * Returns <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>.
-         * This journal mode will only be used if {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING}
-         * flag is not set, otherwise a platform will use "WAL" journal mode.
-         * @see Builder#setJournalMode(String)
+         * set via {@link Builder#setJournalMode(String)}.
          */
         @Nullable
         public String getJournalMode() {
@@ -2799,25 +3005,28 @@
                 return this;
             }
 
-
             /**
              * Sets <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>
-             * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+             * to use.
+             *
+             * <p>Note: If journal mode is not set, the platform will use a manufactured-specified
+             * default which can vary across devices.
              */
             @NonNull
-            public Builder setJournalMode(@NonNull  String journalMode) {
+            public Builder setJournalMode(@JournalMode @NonNull String journalMode) {
                 Objects.requireNonNull(journalMode);
                 mJournalMode = journalMode;
                 return this;
             }
 
-            /**w
+            /**
              * Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
-             * .
-             * @return
+             *
+             * <p>Note: If sync mode is not set, the platform will use a manufactured-specified
+             * default which can vary across devices.
              */
             @NonNull
-            public Builder setSynchronousMode(@NonNull String syncMode) {
+            public Builder setSynchronousMode(@SyncMode @NonNull String syncMode) {
                 Objects.requireNonNull(syncMode);
                 mSyncMode = syncMode;
                 return this;
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 21c21c9..8305843 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -17,9 +17,9 @@
 package android.database.sqlite;
 
 import android.compat.annotation.UnsupportedAppUsage;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Pair;
-
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.function.BinaryOperator;
@@ -132,14 +132,16 @@
      * Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
      * <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
      */
-    public String journalMode;
+    public @SQLiteDatabase.JournalMode String journalMode;
 
     /**
      * Synchronous mode to use.
      * <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
      * or {@link SQLiteGlobal#getWALSyncMode()} depending on journal mode
      */
-    public String syncMode;
+    public @SQLiteDatabase.SyncMode String syncMode;
+
+    public boolean shouldTruncateWalFile;
 
     /**
      * Creates a database configuration with the required parameters for opening a
@@ -217,6 +219,10 @@
         return path.equalsIgnoreCase(MEMORY_DB_PATH);
     }
 
+    public boolean isReadOnlyDatabase() {
+        return (openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
+    }
+
     boolean isLegacyCompatibilityWalEnabled() {
         return journalMode == null && syncMode == null
                 && (openFlags & SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
@@ -232,4 +238,81 @@
     boolean isLookasideConfigSet() {
         return lookasideSlotCount >= 0 && lookasideSlotSize >= 0;
     }
+
+    /**
+     * Resolves the journal mode that should be used when opening a connection to the database.
+     *
+     * Note: assumes openFlags have already been set.
+     *
+     * @return Resolved journal mode that should be used for this database connection or an empty
+     * string if no journal mode should be set.
+     */
+    public @SQLiteDatabase.JournalMode String resolveJournalMode() {
+        if (isReadOnlyDatabase()) {
+            // No need to specify a journal mode when only reading.
+            return "";
+        }
+
+        if (isInMemoryDb()) {
+            if (journalMode != null
+                    && journalMode.equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_OFF)) {
+                return SQLiteDatabase.JOURNAL_MODE_OFF;
+            }
+            return SQLiteDatabase.JOURNAL_MODE_MEMORY;
+        }
+
+        shouldTruncateWalFile = false;
+
+        if (isWalEnabledInternal()) {
+            shouldTruncateWalFile = true;
+            return SQLiteDatabase.JOURNAL_MODE_WAL;
+        } else {
+            // WAL is not explicitly set so use requested journal mode or platform default
+            return this.journalMode != null ? this.journalMode
+                                            : SQLiteGlobal.getDefaultJournalMode();
+        }
+    }
+
+    /**
+     * Resolves the sync mode that should be used when opening a connection to the database.
+     *
+     * Note: assumes openFlags have already been set.
+     * @return Resolved journal mode that should be used for this database connection or null
+     * if no journal mode should be set.
+     */
+    public @SQLiteDatabase.SyncMode String resolveSyncMode() {
+        if (isReadOnlyDatabase()) {
+            // No sync mode will be used since database will be only used for reading.
+            return "";
+        }
+
+        if (isInMemoryDb()) {
+            // No sync mode will be used since database will be in volatile memory
+            return "";
+        }
+
+        if (!TextUtils.isEmpty(syncMode)) {
+            return syncMode;
+        }
+
+        if (isWalEnabledInternal()) {
+            if (isLegacyCompatibilityWalEnabled()) {
+                return SQLiteCompatibilityWalFlags.getWALSyncMode();
+            } else {
+                return SQLiteGlobal.getDefaultSyncMode();
+            }
+        } else {
+            return SQLiteGlobal.getDefaultSyncMode();
+        }
+    }
+
+    private boolean isWalEnabledInternal() {
+        final boolean walEnabled = (openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+        // Use compatibility WAL unless an app explicitly set journal/synchronous mode
+        // or DISABLE_COMPATIBILITY_WAL flag is set
+        final boolean isCompatibilityWalEnabled = isLegacyCompatibilityWalEnabled();
+        return walEnabled || isCompatibilityWalEnabled
+                || (journalMode != null
+                        && journalMode.equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_WAL));
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 1afa0f8..b84a8d2 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -70,7 +70,8 @@
         /**
          * True to enable database performance testing instrumentation.
          */
-        public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE;
+        public static final boolean DEBUG_LOG_SLOW_QUERIES =
+                Log.isLoggable("SQLiteSlowQueries", Log.VERBOSE);
 
         private static final String SLOW_QUERY_THRESHOLD_PROP = "db.log.slow_query_threshold";
 
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 5e2875d..28964fd 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -84,7 +84,7 @@
     /**
      * Gets the default journal mode when WAL is not in use.
      */
-    public static String getDefaultJournalMode() {
+    public static @SQLiteDatabase.JournalMode String getDefaultJournalMode() {
         return SystemProperties.get("debug.sqlite.journalmode",
                 Resources.getSystem().getString(
                 com.android.internal.R.string.db_default_journal_mode));
@@ -102,7 +102,7 @@
     /**
      * Gets the default database synchronization mode when WAL is not in use.
      */
-    public static String getDefaultSyncMode() {
+    public static @SQLiteDatabase.SyncMode String getDefaultSyncMode() {
         // Use the FULL synchronous mode for system processes by default.
         String defaultMode = sDefaultSyncMode;
         if (defaultMode != null) {
@@ -116,7 +116,7 @@
     /**
      * Gets the database synchronization mode when in WAL mode.
      */
-    public static String getWALSyncMode() {
+    public static @SQLiteDatabase.SyncMode String getWALSyncMode() {
         // Use the FULL synchronous mode for system processes by default.
         String defaultMode = sDefaultSyncMode;
         if (defaultMode != null) {
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index cda1638..dae09f0 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -235,7 +235,7 @@
             public Family createFromParcel(Parcel source) {
                 String familyName = source.readString8();
                 List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader());
+                        new ArrayList<>(), Font.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Font.class);
                 return new Family(familyName, fonts);
             }
 
@@ -379,9 +379,9 @@
 
     protected FontUpdateRequest(Parcel in) {
         mType = in.readInt();
-        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
         mSignature = in.readBlob();
-        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader());
+        mFontFamily = in.readParcelable(FontConfig.FontFamily.class.getClassLoader(), android.graphics.fonts.FontUpdateRequest.Family.class);
     }
 
     public @Type int getType() {
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index cad30dd..a4a8f31 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.SurfaceControl;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -129,6 +130,15 @@
     public static final long USAGE_GPU_SAMPLED_IMAGE      = 1 << 8;
     /** Usage: The buffer will be written to by the GPU */
     public static final long USAGE_GPU_COLOR_OUTPUT       = 1 << 9;
+    /**
+     * The buffer will be used as a composer HAL overlay layer.
+     *
+     * This flag is currently only needed when using
+     * {@link android.view.SurfaceControl.Transaction#setBuffer(SurfaceControl, HardwareBuffer)}
+     * to set a buffer. In all other cases, the framework adds this flag
+     * internally to buffers that could be presented in a composer overlay.
+     */
+    public static final long USAGE_COMPOSER_OVERLAY = 1 << 11;
     /** Usage: The buffer must not be used outside of a protected hardware path */
     public static final long USAGE_PROTECTED_CONTENT      = 1 << 14;
     /** Usage: The buffer will be read by a hardware video encoder */
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 3c524b2..7074a2c 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -41,6 +41,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -137,6 +138,11 @@
         public static final int OTHER = SensorPrivacyToggleSourceProto.OTHER;
 
         /**
+         * Constant for SAFETY_HUB.
+         */
+        public static final int SAFETY_HUB = SensorPrivacyToggleSourceProto.SAFETY_HUB;
+
+        /**
          * Source for toggling sensors
          *
          * @hide
@@ -146,7 +152,8 @@
                 SETTINGS,
                 DIALOG,
                 SHELL,
-                OTHER
+                OTHER,
+                SAFETY_HUB
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface Source {}
@@ -409,6 +416,31 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+    public void setSensorPrivacy(@Sensors.Sensor int sensor,
+            boolean enable) {
+        setSensorPrivacy(resolveSourceFromCurrentContext(), sensor, enable,
+                UserHandle.USER_CURRENT);
+    }
+
+    private @Sources.Source int resolveSourceFromCurrentContext() {
+        String packageName = mContext.getOpPackageName();
+        if (Objects.equals(packageName,
+                mContext.getPackageManager().getPermissionControllerPackageName())) {
+            return Sources.SAFETY_HUB;
+        }
+        return Sources.OTHER;
+    }
+
+    /**
+     * Sets sensor privacy to the specified state for an individual sensor.
+     *
+     * @param sensor the sensor which to change the state for
+     * @param enable the state to which sensor privacy should be set.
+     *
+     * @hide
+     */
     @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void setSensorPrivacy(@Sources.Source int source, @Sensors.Sensor int sensor,
@@ -445,7 +477,6 @@
      *
      * @hide
      */
-    @TestApi
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void setSensorPrivacyForProfileGroup(@Sources.Source int source,
             @Sensors.Sensor int sensor, boolean enable) {
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 43ef33e..28046c5 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -151,6 +151,12 @@
     int BIOMETRIC_ERROR_RE_ENROLL = 16;
 
     /**
+     * The privacy setting has been enabled and will block use of the sensor.
+     * @hide
+     */
+    int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
+
+    /**
      * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
      * because the authentication attempt was unsuccessful.
      * @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index fe43c83..fd46f24 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -69,7 +69,7 @@
             BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
             BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
             BIOMETRIC_ERROR_RE_ENROLL,
-            FACE_ERROR_UNKNOWN
+            FACE_ERROR_UNKNOWN,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface FaceError {}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index e6b762a..0c03948 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -65,7 +65,7 @@
         mAuthenticators = in.readInt();
         mDisallowBiometricsIfPolicyExists = in.readBoolean();
         mReceiveSystemEvents = in.readBoolean();
-        mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader());
+        mAllowedSensorIds = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
         mAllowBackgroundAuthentication = in.readBoolean();
         mIgnoreEnrollmentState = in.readBoolean();
     }
diff --git a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
index f365ee6..1490ea1 100644
--- a/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
+++ b/core/java/android/hardware/biometrics/SensorPropertiesInternal.java
@@ -60,7 +60,7 @@
         sensorStrength = in.readInt();
         maxEnrollmentsPerUser = in.readInt();
         componentInfo = new ArrayList<>();
-        in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader());
+        in.readList(componentInfo, ComponentInfoInternal.class.getClassLoader(), android.hardware.biometrics.ComponentInfoInternal.class);
         resetLockoutRequiresHardwareAuthToken = in.readBoolean();
         resetLockoutRequiresChallenge = in.readBoolean();
     }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 3c8b6e9..48a9121 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -2669,7 +2669,8 @@
      * </tbody>
      * </table>
      * <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be
-     * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS media performance class} S,
+     * media performance class 12 or higher by setting
+     * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
      * the primary camera devices (first rear/front camera in the camera ID list) will not
      * support JPEG sizes smaller than 1080p. If the application configures a JPEG stream
      * smaller than 1080p, the camera device will round up the JPEG image size to at least
@@ -2742,9 +2743,11 @@
      * </tbody>
      * </table>
      * <p>For applications targeting SDK version 31 or newer, if the mobile device doesn't declare
-     * to be media performance class S, or if the camera device isn't a primary rear/front
-     * camera, the minimum required output stream configurations are the same as for applications
-     * targeting SDK version older than 31.</p>
+     * to be media performance class 12 or better by setting
+     * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger,
+     * or if the camera device isn't a primary rear/front camera, the minimum required output
+     * stream configurations are the same as for applications targeting SDK version older than
+     * 31.</p>
      * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} for additional
      * mandatory stream configurations on a per-capability basis.</p>
      * <p>Exception on 176x144 (QCIF) resolution: camera devices usually have a fixed capability for
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 93f1d61..c12e819 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -1036,6 +1036,95 @@
     }
 
     /**
+     * Set the brightness level of the flashlight associated with the given cameraId in torch
+     * mode. If the torch is OFF and torchStrength is >= 1, torch will turn ON with the
+     * strength level specified in torchStrength.
+     *
+     * <p>Use
+     * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}
+     * to check whether the camera device supports flash unit strength control or not. If this value
+     * is greater than 1, applications can call this API to control the flashlight brightness level.
+     * </p>
+     *
+     * <p>If {@link #turnOnTorchWithStrengthLevel} is called to change the brightness level of the
+     * flash unit {@link CameraManager.TorchCallback#onTorchStrengthLevelChanged} will be invoked.
+     * If the new desired strength level is same as previously set level, then this callback will
+     * not be invoked.
+     * If the torch is OFF and {@link #turnOnTorchWithStrengthLevel} is called with level >= 1,
+     * the torch will be turned ON with that brightness level. In this case
+     * {@link CameraManager.TorchCallback#onTorchModeChanged} will also be invoked.
+     * </p>
+     *
+     * <p>When the torch is turned OFF via {@link #setTorchMode}, the flashlight brightness level
+     * will reset to default value
+     * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}
+     * In this case the {@link CameraManager.TorchCallback#onTorchStrengthLevelChanged} will not be
+     * invoked.
+     * </p>
+     *
+     * <p>If torch is enabled via {@link #setTorchMode} after calling
+     * {@link #turnOnTorchWithStrengthLevel} with level N then the flash unit will have the
+     * brightness level N.
+     * Since multiple applications are free to call {@link #setTorchMode}, when the latest
+     * application that turned ON the torch mode exits, the torch mode will be turned OFF
+     * and in this case the brightness level will reset to default level.
+     * </p>
+     *
+     * @param cameraId
+     *             The unique identifier of the camera device that the flash unit belongs to.
+     * @param torchStrength
+     *             The desired brightness level to be set for the flash unit in the range 1 to
+     *             {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}.
+     *
+     * @throws CameraAccessException if it failed to access the flash unit.
+     *             {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
+     *             is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
+     *             other camera resources needed to turn on the torch mode are in use.
+     *             {@link CameraAccessException#CAMERA_DISCONNECTED} will be thrown if camera
+     *             service is not available.
+     * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
+     *              or previously available camera device, the camera device doesn't have a
+     *              flash unit or if torchStrength is not within the range i.e. is greater than
+     *              the maximum level
+     *              {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL}
+     *              or <= 0.
+     *
+     */
+    public void turnOnTorchWithStrengthLevel(@NonNull String cameraId, int torchStrength)
+            throws CameraAccessException {
+        if (CameraManagerGlobal.sCameraServiceDisabled) {
+            throw new IllegalArgumentException("No camera available on device");
+        }
+        CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength);
+    }
+
+    /**
+     * Returns the brightness level of the flash unit associated with the cameraId.
+     *
+     * @param cameraId
+     *              The unique identifier of the camera device that the flash unit belongs to.
+     * @return The brightness level of the flash unit associated with cameraId.
+     *         When the torch is turned OFF, the strength level will reset to a default level
+     *         {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}.
+     *         In this case the return value will be
+     *         {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}
+     *         rather than 0.
+     *
+     * @throws CameraAccessException if it failed to access the flash unit.
+     * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
+     *              or previously available camera device, or the camera device doesn't have a
+     *              flash unit.
+     *
+     */
+    public int getTorchStrengthLevel(@NonNull String cameraId)
+            throws CameraAccessException {
+        if (CameraManagerGlobal.sCameraServiceDisabled) {
+            throw new IllegalArgumentException("No camera available on device.");
+        }
+        return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
+    }
+
+    /**
      * A callback for camera devices becoming available or unavailable to open.
      *
      * <p>Cameras become available when they are no longer in use, or when a new
@@ -1239,6 +1328,24 @@
         public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
             // default empty implementation
         }
+
+        /**
+         * A camera's flash unit brightness level has been changed in torch mode via
+         * {@link #turnOnTorchWithStrengthLevel}. When the torch is turned OFF, this
+         * callback will not be triggered even though the torch strength level resets to
+         * default value
+         * {@link android.hardware.camera2.CameraCharacteristics#FLASH_INFO_STRENGTH_DEFAULT_LEVEL}
+         *
+         * <p>The default implementation of this method does nothing.</p>
+         *
+         * @param cameraId The unique identifier of the camera whose flash unit brightness level has
+         * been changed.
+         *
+         * @param newStrengthLevel The brightness level of the flash unit that has been changed to.
+         */
+        public void onTorchStrengthLevelChanged(@NonNull String cameraId, int newStrengthLevel) {
+            // default empty implementation
+        }
     }
 
     /**
@@ -1642,6 +1749,10 @@
                 public void onTorchStatusChanged(int status, String id) throws RemoteException {
                 }
                 @Override
+                public void onTorchStrengthLevelChanged(String id, int newStrengthLevel)
+                        throws RemoteException {
+                }
+                @Override
                 public void onCameraAccessPrioritiesChanged() {
                 }
                 @Override
@@ -1825,6 +1936,57 @@
             }
         }
 
+        public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength) throws
+                CameraAccessException {
+            synchronized(mLock) {
+
+                if (cameraId == null) {
+                    throw new IllegalArgumentException("cameraId was null");
+                }
+
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                        "Camera service is currently unavailable.");
+                }
+
+                try {
+                    cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength,
+                            mTorchClientBinder);
+                } catch(ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
+        }
+
+        public int getTorchStrengthLevel(String cameraId) throws CameraAccessException {
+            int torchStrength = 0;
+            synchronized(mLock) {
+                if (cameraId == null) {
+                    throw new IllegalArgumentException("cameraId was null");
+                }
+
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                        "Camera service is currently unavailable.");
+                }
+
+                try {
+                    torchStrength = cameraService.getTorchStrengthLevel(cameraId);
+                } catch(ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
+            return torchStrength;
+        }
+
         private void handleRecoverableSetupErrors(ServiceSpecificException e) {
             switch (e.errorCode) {
                 case ICameraService.ERROR_DISCONNECTED:
@@ -1984,6 +2146,18 @@
             }
         }
 
+        private void postSingleTorchStrengthLevelUpdate(final TorchCallback callback,
+                 final Executor executor, final String id, final int newStrengthLevel) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                executor.execute(() -> {
+                    callback.onTorchStrengthLevelChanged(id, newStrengthLevel);
+                });
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
         /**
          * Send the state of all known cameras to the provided listener, to initialize
          * the listener's knowledge of camera state.
@@ -2167,6 +2341,22 @@
             }
         } // onTorchStatusChangedLocked
 
+        private void onTorchStrengthLevelChangedLocked(String cameraId, int newStrengthLevel) {
+            if (DEBUG) {
+
+                Log.v(TAG,
+                        String.format("Camera id %s has torch strength level changed to %d",
+                            cameraId, newStrengthLevel));
+            }
+
+            final int callbackCount = mTorchCallbackMap.size();
+            for (int i = 0; i < callbackCount; i++) {
+                final Executor executor = mTorchCallbackMap.valueAt(i);
+                final TorchCallback callback = mTorchCallbackMap.keyAt(i);
+                postSingleTorchStrengthLevelUpdate(callback, executor, cameraId, newStrengthLevel);
+            }
+        } // onTorchStrengthLevelChanged
+
         /**
          * Register a callback to be notified about camera device availability with the
          * global listener singleton.
@@ -2258,6 +2448,14 @@
         }
 
         @Override
+        public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel)
+                throws RemoteException {
+            synchronized (mLock) {
+                onTorchStrengthLevelChangedLocked(cameraId, newStrengthLevel);
+            }
+        }
+
+        @Override
         public void onCameraAccessPrioritiesChanged() {
             synchronized (mLock) {
                 final int callbackCount = mCallbackMap.size();
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4c81f9c..0037464 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -30,6 +30,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.app.KeyguardManager;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -902,8 +903,16 @@
             @NonNull VirtualDisplayConfig virtualDisplayConfig,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
             @Nullable Context windowContext) {
-        return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,
-                handler, windowContext);
+        return mGlobal.createVirtualDisplay(mContext, projection, null /* virtualDevice */,
+                virtualDisplayConfig, callback, handler, windowContext);
+    }
+
+    /** @hide */
+    public VirtualDisplay createVirtualDisplay(@Nullable IVirtualDevice virtualDevice,
+            @NonNull VirtualDisplayConfig virtualDisplayConfig,
+            @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+        return mGlobal.createVirtualDisplay(mContext, null /* projection */, virtualDevice,
+                virtualDisplayConfig, callback, handler, null);
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 75155bb..01833fd 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PropertyInvalidatedCache;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
@@ -582,14 +583,14 @@
     }
 
     public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection,
-            @NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback,
-            Handler handler, @Nullable Context windowContext) {
+            IVirtualDevice virtualDevice, @NonNull VirtualDisplayConfig virtualDisplayConfig,
+            VirtualDisplay.Callback callback, Handler handler, @Nullable Context windowContext) {
         VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
         IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
         int displayId;
         try {
             displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper,
-                    projectionToken, context.getPackageName());
+                    projectionToken, virtualDevice, context.getPackageName());
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 2985c75..83e1061 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.graphics.Point;
 import android.hardware.SensorManager;
-import android.media.projection.IMediaProjection;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -381,31 +380,6 @@
     public abstract void onEarlyInteractivityChange(boolean interactive);
 
     /**
-     * A special API for creates a virtual display with a DisplayPolicyController in system_server.
-     * <p>
-     * If this method is called without original calling uid, the caller must enforce the
-     * corresponding permissions according to the flags.
-     *   {@link android.Manifest.permission#CAPTURE_VIDEO_OUTPUT}
-     *   {@link android.Manifest.permission#CAPTURE_SECURE_VIDEO_OUTPUT}
-     *   {@link android.Manifest.permission#ADD_TRUSTED_DISPLAY}
-     *   {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW}
-     * </p>
-     *
-     * @param virtualDisplayConfig The arguments for the virtual display configuration. See
-     *                             {@link VirtualDisplayConfig} for using it.
-     * @param callback Callback to call when the virtual display's state changes, or null if none.
-     * @param projection MediaProjection token.
-     * @param packageName The package name of the app.
-     * @param controller The DisplayWindowPolicyControl that can control what contents are
-     *                   allowed to be displayed.
-     * @return The newly created virtual display id , or {@link Display#INVALID_DISPLAY} if the
-     * virtual display cannot be created.
-     */
-    public abstract int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
-            IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
-            DisplayWindowPolicyController controller);
-
-    /**
      * Get {@link DisplayWindowPolicyController} associated to the {@link DisplayInfo#displayId}
      *
      * @param displayId The id of the display.
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 0d5f1af..82b31d4 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.display;
 
+import android.companion.virtual.IVirtualDevice;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Point;
 import android.hardware.display.BrightnessConfiguration;
@@ -86,10 +87,10 @@
     void requestColorMode(int displayId, int colorMode);
 
     // Requires CAPTURE_VIDEO_OUTPUT, CAPTURE_SECURE_VIDEO_OUTPUT, or an appropriate
-    // MediaProjection token for certain combinations of flags.
+    // MediaProjection token or VirtualDevice for certain combinations of flags.
     int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
             in IVirtualDisplayCallback callback, in IMediaProjection projectionToken,
-            String packageName);
+            in IVirtualDevice virtualDevice, String packageName);
 
     // No permissions required, but must be same Uid as the creator.
     void resizeVirtualDisplay(in IVirtualDisplayCallback token,
diff --git a/core/java/android/hardware/face/FaceAuthenticationFrame.java b/core/java/android/hardware/face/FaceAuthenticationFrame.java
index f39d634..a53aad7 100644
--- a/core/java/android/hardware/face/FaceAuthenticationFrame.java
+++ b/core/java/android/hardware/face/FaceAuthenticationFrame.java
@@ -46,7 +46,7 @@
     }
 
     private FaceAuthenticationFrame(@NonNull Parcel source) {
-        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class);
     }
 
     @Override
diff --git a/core/java/android/hardware/face/FaceEnrollFrame.java b/core/java/android/hardware/face/FaceEnrollFrame.java
index 822a579..bbccee2 100644
--- a/core/java/android/hardware/face/FaceEnrollFrame.java
+++ b/core/java/android/hardware/face/FaceEnrollFrame.java
@@ -73,9 +73,9 @@
     }
 
     private FaceEnrollFrame(@NonNull Parcel source) {
-        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader());
+        mCell = source.readParcelable(FaceEnrollCell.class.getClassLoader(), android.hardware.face.FaceEnrollCell.class);
         mStage = source.readInt();
-        mData = source.readParcelable(FaceDataFrame.class.getClassLoader());
+        mData = source.readParcelable(FaceDataFrame.class.getClassLoader(), android.hardware.face.FaceDataFrame.class);
     }
 
     @Override
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/hardware/input/VirtualKeyEvent.aidl
similarity index 90%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/hardware/input/VirtualKeyEvent.aidl
index 0abc7ec..5b3ee0c 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/hardware/input/VirtualKeyEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.hardware.input;
 
-parcelable TsRequest;
+parcelable VirtualKeyEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
new file mode 100644
index 0000000..d875156
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -0,0 +1,152 @@
+/*
+ * 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.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.KeyEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a keyboard interaction originating from a remote device.
+ *
+ * When the user presses a key, an {@code ACTION_DOWN} event should be reported. When the user
+ * releases the key, an {@code ACTION_UP} event should be reported.
+ *
+ * See {@link android.view.KeyEvent}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualKeyEvent implements Parcelable {
+
+    /** @hide */
+    public static final int ACTION_UNKNOWN = -1;
+    /** Action indicating the given key has been pressed. */
+    public static final int ACTION_DOWN = KeyEvent.ACTION_DOWN;
+    /** Action indicating the previously pressed key has been lifted. */
+    public static final int ACTION_UP = KeyEvent.ACTION_UP;
+
+    /** @hide */
+    @IntDef(prefix = { "ACTION_" }, value = {
+            ACTION_UNKNOWN,
+            ACTION_DOWN,
+            ACTION_UP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {
+    }
+
+    private final @Action int mAction;
+    private final int mKeyCode;
+
+    private VirtualKeyEvent(@Action int action, int keyCode) {
+        mAction = action;
+        mKeyCode = keyCode;
+    }
+
+    private VirtualKeyEvent(@NonNull Parcel parcel) {
+        mAction = parcel.readInt();
+        mKeyCode = parcel.readInt();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mAction);
+        parcel.writeInt(mKeyCode);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the key code associated with this event.
+     */
+    public int getKeyCode() {
+        return mKeyCode;
+    }
+
+    /**
+     * Returns the action associated with this event.
+     */
+    public @Action int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Builder for {@link VirtualKeyEvent}.
+     */
+    public static final class Builder {
+
+        private @Action int mAction = ACTION_UNKNOWN;
+        private int mKeyCode = -1;
+
+        /**
+         * Creates a {@link VirtualKeyEvent} object with the current builder configuration.
+         */
+        public @NonNull VirtualKeyEvent build() {
+            if (mAction == ACTION_UNKNOWN || mKeyCode == -1) {
+                throw new IllegalArgumentException(
+                        "Cannot build virtual key event with unset fields");
+            }
+            return new VirtualKeyEvent(mAction, mKeyCode);
+        }
+
+        /**
+         * Sets the Android key code of the event. The set of allowed characters include digits 0-9,
+         * characters A-Z, and standard punctuation, as well as numpad keys, function keys F1-F12,
+         * and meta keys (caps lock, shift, etc.).
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setKeyCode(int keyCode) {
+            mKeyCode = keyCode;
+            return this;
+        }
+
+        /**
+         * Sets the action of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setAction(@Action int action) {
+            if (action != ACTION_DOWN && action != ACTION_UP) {
+                throw new IllegalArgumentException("Unsupported action type");
+            }
+            mAction = action;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualKeyEvent> CREATOR =
+            new Parcelable.Creator<VirtualKeyEvent>() {
+        public VirtualKeyEvent createFromParcel(Parcel source) {
+            return new VirtualKeyEvent(source);
+        }
+
+        public VirtualKeyEvent[] newArray(int size) {
+            return new VirtualKeyEvent[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
new file mode 100644
index 0000000..ee9b659
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -0,0 +1,72 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.Closeable;
+
+/**
+ * A virtual keyboard representing a key input mechanism on a remote device, such as a built-in
+ * keyboard on a laptop, a software keyboard on a tablet, or a keypad on a TV remote control.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualKeyboard implements Closeable {
+
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /** @hide */
+    public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) {
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void close() {
+        try {
+            mVirtualDevice.unregisterInputDevice(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends a key event to the system.
+     *
+     * @param event the event to send
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendKeyEvent(@NonNull VirtualKeyEvent event) {
+        try {
+            mVirtualDevice.sendKeyEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
new file mode 100644
index 0000000..6599dd2
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -0,0 +1,102 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.MotionEvent;
+
+import java.io.Closeable;
+
+/**
+ * A virtual mouse representing a relative input mechanism on a remote device, such as a mouse or
+ * trackpad.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualMouse implements Closeable {
+
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /** @hide */
+    public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) {
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void close() {
+        try {
+            mVirtualDevice.unregisterInputDevice(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Send a mouse button event to the system.
+     *
+     * @param event the event
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) {
+        try {
+            mVirtualDevice.sendButtonEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends a scrolling event to the system. See {@link MotionEvent#AXIS_VSCROLL} and
+     * {@link MotionEvent#AXIS_SCROLL}.
+     *
+     * @param event the event
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) {
+        try {
+            mVirtualDevice.sendScrollEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends a relative movement event to the system.
+     *
+     * @param event the event
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) {
+        try {
+            mVirtualDevice.sendRelativeEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl
similarity index 89%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/hardware/input/VirtualMouseButtonEvent.aidl
index 0abc7ec..ebcf5aa 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.hardware.input;
 
-parcelable TsRequest;
+parcelable VirtualMouseButtonEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
new file mode 100644
index 0000000..2e094cf
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
@@ -0,0 +1,180 @@
+/*
+ * 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.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a mouse button click interaction originating from a remote device.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseButtonEvent implements Parcelable {
+
+    /** @hide */
+    public static final int ACTION_UNKNOWN = -1;
+    /** Action indicating the mouse button has been pressed. */
+    public static final int ACTION_BUTTON_PRESS = MotionEvent.ACTION_BUTTON_PRESS;
+    /** Action indicating the mouse button has been released. */
+    public static final int ACTION_BUTTON_RELEASE = MotionEvent.ACTION_BUTTON_RELEASE;
+    /** @hide */
+    @IntDef(prefix = {"ACTION_"}, value = {
+            ACTION_UNKNOWN,
+            ACTION_BUTTON_PRESS,
+            ACTION_BUTTON_RELEASE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {}
+
+    /** @hide */
+    public static final int BUTTON_UNKNOWN = -1;
+    /** Action indicating the mouse button involved in this event is in the left position. */
+    public static final int BUTTON_PRIMARY = MotionEvent.BUTTON_PRIMARY;
+    /** Action indicating the mouse button involved in this event is in the middle position. */
+    public static final int BUTTON_TERTIARY = MotionEvent.BUTTON_TERTIARY;
+    /** Action indicating the mouse button involved in this event is in the right position. */
+    public static final int BUTTON_SECONDARY = MotionEvent.BUTTON_SECONDARY;
+    /**
+     * Action indicating the mouse button involved in this event is intended to go back to the
+     * previous.
+     */
+    public static final int BUTTON_BACK = MotionEvent.BUTTON_BACK;
+    /**
+     * Action indicating the mouse button involved in this event is intended to move forward to the
+     * next.
+     */
+    public static final int BUTTON_FORWARD = MotionEvent.BUTTON_FORWARD;
+    /** @hide */
+    @IntDef(prefix = {"BUTTON_"}, value = {
+            BUTTON_UNKNOWN,
+            BUTTON_PRIMARY,
+            BUTTON_TERTIARY,
+            BUTTON_SECONDARY,
+            BUTTON_BACK,
+            BUTTON_FORWARD,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Button {}
+
+    private final @Action int mAction;
+    private final @Button int mButtonCode;
+
+    private VirtualMouseButtonEvent(@Action int action, @Button int buttonCode) {
+        mAction = action;
+        mButtonCode = buttonCode;
+    }
+
+    private VirtualMouseButtonEvent(@NonNull Parcel parcel) {
+        mAction = parcel.readInt();
+        mButtonCode = parcel.readInt();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeInt(mAction);
+        parcel.writeInt(mButtonCode);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the button code associated with this event.
+     */
+    public @Button int getButtonCode() {
+        return mButtonCode;
+    }
+
+    /**
+     * Returns the action associated with this event.
+     */
+    public @Action int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Builder for {@link VirtualMouseButtonEvent}.
+     */
+    public static final class Builder {
+
+        private @Action int mAction = ACTION_UNKNOWN;
+        private @Button int mButtonCode = -1;
+
+        /**
+         * Creates a {@link VirtualMouseButtonEvent} object with the current builder configuration.
+         */
+        public @NonNull VirtualMouseButtonEvent build() {
+            if (mAction == ACTION_UNKNOWN || mButtonCode == -1) {
+                throw new IllegalArgumentException(
+                        "Cannot build virtual mouse button event with unset fields");
+            }
+            return new VirtualMouseButtonEvent(mAction, mButtonCode);
+        }
+
+        /**
+         * Sets the button code of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setButtonCode(int buttonCode) {
+            if (buttonCode != BUTTON_PRIMARY
+                    && buttonCode != BUTTON_TERTIARY
+                    && buttonCode != BUTTON_SECONDARY
+                    && buttonCode != BUTTON_BACK
+                    && buttonCode != BUTTON_FORWARD) {
+                throw new IllegalArgumentException("Unsupported mouse button code");
+            }
+            mButtonCode = buttonCode;
+            return this;
+        }
+
+        /**
+         * Sets the action of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setAction(@Action int action) {
+            if (action != ACTION_BUTTON_PRESS && action != ACTION_BUTTON_RELEASE) {
+                throw new IllegalArgumentException("Unsupported mouse button action type");
+            }
+            mAction = action;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualMouseButtonEvent> CREATOR =
+            new Parcelable.Creator<VirtualMouseButtonEvent>() {
+                public VirtualMouseButtonEvent createFromParcel(Parcel source) {
+                    return new VirtualMouseButtonEvent(source);
+                }
+
+                public VirtualMouseButtonEvent[] newArray(int size) {
+                    return new VirtualMouseButtonEvent[size];
+                }
+            };
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl
similarity index 89%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl
index 0abc7ec..1095858 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.hardware.input;
 
-parcelable TsRequest;
+parcelable VirtualMouseRelativeEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
new file mode 100644
index 0000000..65ed1f2
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
@@ -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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * An event describing a mouse movement interaction originating from a remote device.
+ *
+ * See {@link android.view.MotionEvent}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseRelativeEvent implements Parcelable {
+
+    private final float mRelativeX;
+    private final float mRelativeY;
+
+    private VirtualMouseRelativeEvent(float relativeX, float relativeY) {
+        mRelativeX = relativeX;
+        mRelativeY = relativeY;
+    }
+
+    private VirtualMouseRelativeEvent(@NonNull Parcel parcel) {
+        mRelativeX = parcel.readFloat();
+        mRelativeY = parcel.readFloat();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeFloat(mRelativeX);
+        parcel.writeFloat(mRelativeY);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the relative x-axis movement, in pixels.
+     */
+    public float getRelativeX() {
+        return mRelativeX;
+    }
+
+    /**
+     * Returns the relative x-axis movement, in pixels.
+     */
+    public float getRelativeY() {
+        return mRelativeY;
+    }
+
+    /**
+     * Builder for {@link VirtualMouseRelativeEvent}.
+     */
+    public static final class Builder {
+
+        private float mRelativeX;
+        private float mRelativeY;
+
+        /**
+         * Creates a {@link VirtualMouseRelativeEvent} object with the current builder
+         * configuration.
+         */
+        public @NonNull VirtualMouseRelativeEvent build() {
+            return new VirtualMouseRelativeEvent(mRelativeX, mRelativeY);
+        }
+
+        /**
+         * Sets the relative x-axis movement, in pixels.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setRelativeX(float relativeX) {
+            mRelativeX = relativeX;
+            return this;
+        }
+
+        /**
+         * Sets the relative y-axis movement, in pixels.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setRelativeY(float relativeY) {
+            mRelativeY = relativeY;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualMouseRelativeEvent> CREATOR =
+            new Parcelable.Creator<VirtualMouseRelativeEvent>() {
+                public VirtualMouseRelativeEvent createFromParcel(Parcel source) {
+                    return new VirtualMouseRelativeEvent(source);
+                }
+
+                public VirtualMouseRelativeEvent[] newArray(int size) {
+                    return new VirtualMouseRelativeEvent[size];
+                }
+            };
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl
similarity index 89%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/hardware/input/VirtualMouseScrollEvent.aidl
index 0abc7ec..13177ef 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.hardware.input;
 
-parcelable TsRequest;
+parcelable VirtualMouseScrollEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
new file mode 100644
index 0000000..1723259
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
@@ -0,0 +1,129 @@
+/*
+ * 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.input;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * An event describing a mouse scroll interaction originating from a remote device.
+ *
+ * See {@link android.view.MotionEvent}.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseScrollEvent implements Parcelable {
+
+    private final float mXAxisMovement;
+    private final float mYAxisMovement;
+
+    private VirtualMouseScrollEvent(float xAxisMovement, float yAxisMovement) {
+        mXAxisMovement = xAxisMovement;
+        mYAxisMovement = yAxisMovement;
+    }
+
+    private VirtualMouseScrollEvent(@NonNull Parcel parcel) {
+        mXAxisMovement = parcel.readFloat();
+        mYAxisMovement = parcel.readFloat();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+        parcel.writeFloat(mXAxisMovement);
+        parcel.writeFloat(mYAxisMovement);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+     * indicate scrolling upward; negative values, downward.
+     */
+    public float getXAxisMovement() {
+        return mXAxisMovement;
+    }
+
+    /**
+     * Returns the y-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+     * indicate scrolling towards the right; negative values, to the left.
+     */
+    public float getYAxisMovement() {
+        return mYAxisMovement;
+    }
+
+    /**
+     * Builder for {@link VirtualMouseScrollEvent}.
+     */
+    public static final class Builder {
+
+        private float mXAxisMovement;
+        private float mYAxisMovement;
+
+        /**
+         * Creates a {@link VirtualMouseScrollEvent} object with the current builder configuration.
+         */
+        public @NonNull VirtualMouseScrollEvent build() {
+            return new VirtualMouseScrollEvent(mXAxisMovement, mYAxisMovement);
+        }
+
+        /**
+         * Sets the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+         * indicate scrolling upward; negative values, downward.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setXAxisMovement(
+                @FloatRange(from = -1.0f, to = 1.0f) float xAxisMovement) {
+            Preconditions.checkArgumentInRange(xAxisMovement, -1f, 1f, "xAxisMovement");
+            mXAxisMovement = xAxisMovement;
+            return this;
+        }
+
+        /**
+         * Sets the y-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
+         * indicate scrolling towards the right; negative values, to the left.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setYAxisMovement(
+                @FloatRange(from = -1.0f, to = 1.0f) float yAxisMovement) {
+            Preconditions.checkArgumentInRange(yAxisMovement, -1f, 1f, "yAxisMovement");
+            mYAxisMovement = yAxisMovement;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualMouseScrollEvent> CREATOR =
+            new Parcelable.Creator<VirtualMouseScrollEvent>() {
+                public VirtualMouseScrollEvent createFromParcel(Parcel source) {
+                    return new VirtualMouseScrollEvent(source);
+                }
+
+                public VirtualMouseScrollEvent[] newArray(int size) {
+                    return new VirtualMouseScrollEvent[size];
+                }
+            };
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/hardware/input/VirtualTouchEvent.aidl
similarity index 90%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/hardware/input/VirtualTouchEvent.aidl
index 0abc7ec..03c82e3 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/hardware/input/VirtualTouchEvent.aidl
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.hardware.input;
 
-parcelable TsRequest;
+parcelable VirtualTouchEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
new file mode 100644
index 0000000..c7450d8
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -0,0 +1,299 @@
+/*
+ * 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.input;
+
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An event describing a touchscreen interaction originating from a remote device.
+ *
+ * The pointer id, tool type, action, and location are required; pressure and main axis size are
+ * optional.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualTouchEvent implements Parcelable {
+
+    /** @hide */
+    public static final int TOOL_TYPE_UNKNOWN = MotionEvent.TOOL_TYPE_UNKNOWN;
+    /** Tool type indicating that the user's finger is the origin of the event. */
+    public static final int TOOL_TYPE_FINGER = MotionEvent.TOOL_TYPE_FINGER;
+    /**
+     * Tool type indicating that a user's palm (or other input mechanism to be rejected) is the
+     * origin of the event.
+     */
+    public static final int TOOL_TYPE_PALM = MotionEvent.TOOL_TYPE_PALM;
+    /** @hide */
+    @IntDef(prefix = { "TOOL_TYPE_" }, value = {
+            TOOL_TYPE_UNKNOWN,
+            TOOL_TYPE_FINGER,
+            TOOL_TYPE_PALM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ToolType {}
+
+    /** @hide */
+    public static final int ACTION_UNKNOWN = -1;
+    /** Action indicating the tool has been pressed down to the touchscreen. */
+    public static final int ACTION_DOWN = MotionEvent.ACTION_DOWN;
+    /** Action indicating the tool has been lifted from the touchscreen. */
+    public static final int ACTION_UP = MotionEvent.ACTION_UP;
+    /** Action indicating the tool has been moved along the face of the touchscreen. */
+    public static final int ACTION_MOVE = MotionEvent.ACTION_MOVE;
+    /** Action indicating the tool cancelled the current movement. */
+    public static final int ACTION_CANCEL = MotionEvent.ACTION_CANCEL;
+    /** @hide */
+    @IntDef(prefix = { "ACTION_" }, value = {
+            ACTION_UNKNOWN,
+            ACTION_DOWN,
+            ACTION_UP,
+            ACTION_MOVE,
+            ACTION_CANCEL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Action {}
+
+    private final int mPointerId;
+    private final @ToolType int mToolType;
+    private final @Action int mAction;
+    private final float mX;
+    private final float mY;
+    private final float mPressure;
+    private final float mMajorAxisSize;
+
+    private VirtualTouchEvent(int pointerId, @ToolType int toolType, @Action int action,
+            float x, float y, float pressure, float majorAxisSize) {
+        mPointerId = pointerId;
+        mToolType = toolType;
+        mAction = action;
+        mX = x;
+        mY = y;
+        mPressure = pressure;
+        mMajorAxisSize = majorAxisSize;
+    }
+
+    private VirtualTouchEvent(@NonNull Parcel parcel) {
+        mPointerId = parcel.readInt();
+        mToolType = parcel.readInt();
+        mAction = parcel.readInt();
+        mX = parcel.readFloat();
+        mY = parcel.readFloat();
+        mPressure = parcel.readFloat();
+        mMajorAxisSize = parcel.readFloat();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPointerId);
+        dest.writeInt(mToolType);
+        dest.writeInt(mAction);
+        dest.writeFloat(mX);
+        dest.writeFloat(mY);
+        dest.writeFloat(mPressure);
+        dest.writeFloat(mMajorAxisSize);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the pointer id associated with this event.
+     */
+    public int getPointerId() {
+        return mPointerId;
+    }
+
+    /**
+     * Returns the tool type associated with this event.
+     */
+    public @ToolType int getToolType() {
+        return mToolType;
+    }
+
+    /**
+     * Returns the action associated with this event.
+     */
+    public @Action int getAction() {
+        return mAction;
+    }
+
+    /**
+     * Returns the x-axis location associated with this event.
+     */
+    public float getX() {
+        return mX;
+    }
+
+    /**
+     * Returns the y-axis location associated with this event.
+     */
+    public float getY() {
+        return mY;
+    }
+
+    /**
+     * Returns the pressure associated with this event. Returns {@link Float#NaN} if omitted.
+     */
+    public float getPressure() {
+        return mPressure;
+    }
+
+    /**
+     * Returns the major axis size associated with this event. Returns {@link Float#NaN} if omitted.
+     */
+    public float getMajorAxisSize() {
+        return mMajorAxisSize;
+    }
+
+    /**
+     * Builder for {@link VirtualTouchEvent}.
+     */
+    public static final class Builder {
+
+        private @ToolType int mToolType = TOOL_TYPE_UNKNOWN;
+        private int mPointerId = MotionEvent.INVALID_POINTER_ID;
+        private @Action int mAction = ACTION_UNKNOWN;
+        private float mX = Float.NaN;
+        private float mY = Float.NaN;
+        private float mPressure = Float.NaN;
+        private float mMajorAxisSize = Float.NaN;
+
+        /**
+         * Creates a {@link VirtualTouchEvent} object with the current builder configuration.
+         */
+        public @NonNull VirtualTouchEvent build() {
+            if (mToolType == TOOL_TYPE_UNKNOWN || mPointerId == MotionEvent.INVALID_POINTER_ID
+                    || mAction == ACTION_UNKNOWN || Float.isNaN(mX) || Float.isNaN(mY)) {
+                throw new IllegalArgumentException(
+                        "Cannot build virtual touch event with unset required fields");
+            }
+            if ((mToolType == TOOL_TYPE_PALM && mAction != ACTION_CANCEL)
+                    || (mAction == ACTION_CANCEL && mToolType != TOOL_TYPE_PALM)) {
+                throw new IllegalArgumentException(
+                        "ACTION_CANCEL and TOOL_TYPE_PALM must always appear together");
+            }
+            return new VirtualTouchEvent(mPointerId, mToolType, mAction, mX, mY, mPressure,
+                    mMajorAxisSize);
+        }
+
+        /**
+         * Sets the pointer id of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setPointerId(int pointerId) {
+            mPointerId = pointerId;
+            return this;
+        }
+
+        /**
+         * Sets the tool type of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setToolType(@ToolType int toolType) {
+            if (toolType != TOOL_TYPE_FINGER && toolType != TOOL_TYPE_PALM) {
+                throw new IllegalArgumentException("Unsupported touch event tool type");
+            }
+            mToolType = toolType;
+            return this;
+        }
+
+        /**
+         * Sets the action of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setAction(@Action int action) {
+            if (action != ACTION_DOWN && action != ACTION_UP && action != ACTION_MOVE
+                    && action != ACTION_CANCEL) {
+                throw new IllegalArgumentException("Unsupported touch event action type");
+            }
+            mAction = action;
+            return this;
+        }
+
+        /**
+         * Sets the x-axis location of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setX(float absX) {
+            mX = absX;
+            return this;
+        }
+
+        /**
+         * Sets the y-axis location of the event.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setY(float absY) {
+            mY = absY;
+            return this;
+        }
+
+        /**
+         * Sets the pressure of the event. This field is optional and can be omitted.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setPressure(@FloatRange(from = 0f) float pressure) {
+            if (pressure < 0f) {
+                throw new IllegalArgumentException("Touch event pressure cannot be negative");
+            }
+            mPressure = pressure;
+            return this;
+        }
+
+        /**
+         * Sets the major axis size of the event. This field is optional and can be omitted.
+         *
+         * @return this builder, to allow for chaining of calls
+         */
+        public @NonNull Builder setMajorAxisSize(@FloatRange(from = 0f) float majorAxisSize) {
+            if (majorAxisSize < 0f) {
+                throw new IllegalArgumentException(
+                        "Touch event major axis size cannot be negative");
+            }
+            mMajorAxisSize = majorAxisSize;
+            return this;
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<VirtualTouchEvent> CREATOR =
+            new Parcelable.Creator<VirtualTouchEvent>() {
+        public VirtualTouchEvent createFromParcel(Parcel source) {
+            return new VirtualTouchEvent(source);
+        }
+        public VirtualTouchEvent[] newArray(int size) {
+            return new VirtualTouchEvent[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java
new file mode 100644
index 0000000..c8d602a
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -0,0 +1,71 @@
+/*
+ * 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.input;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.io.Closeable;
+
+/**
+ * A virtual touchscreen representing a touch-based display input mechanism on a remote device.
+ *
+ * This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualTouchscreen implements Closeable {
+
+    private final IVirtualDevice mVirtualDevice;
+    private final IBinder mToken;
+
+    /** @hide */
+    public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) {
+        mVirtualDevice = virtualDevice;
+        mToken = token;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void close() {
+        try {
+            mVirtualDevice.unregisterInputDevice(mToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends a touch event to the system.
+     *
+     * @param event the event to send
+     */
+    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void sendTouchEvent(@NonNull VirtualTouchEvent event) {
+        try {
+            mVirtualDevice.sendTouchEvent(mToken, event);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index bcdd519..a525f58 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -60,6 +60,8 @@
      */
     private final boolean mPersistent;
 
+    private Integer mId = null;
+
     /* package */ ContextHubClient(ContextHubInfo hubInfo, boolean persistent) {
         mAttachedHub = hubInfo;
         mPersistent = persistent;
@@ -85,6 +87,11 @@
         }
 
         mClientProxy = clientProxy;
+        try {
+            mId = Integer.valueOf(mClientProxy.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -98,6 +105,31 @@
     }
 
     /**
+     * Returns the system-wide unique identifier for this ContextHubClient.
+     *
+     * This value can be used as an identifier for the messaging channel between a
+     * ContextHubClient and the Context Hub. This may be used as a routing mechanism
+     * between various ContextHubClient objects within an application.
+     *
+     * The value returned by this method will remain the same if it is associated with
+     * the same client reference at the ContextHubService (for instance, the ID of a
+     * PendingIntent ContextHubClient will remain the same even if the local object
+     * has been regenerated with the equivalent PendingIntent). If the ContextHubClient
+     * is newly generated (e.g. any regeneration of a callback client, or generation
+     * of a non-equal PendingIntent client), the ID will not be the same.
+     *
+     * @return The ID of this ContextHubClient.
+     *
+     * @throws IllegalStateException if the ID was not set internally.
+     */
+    public int getId() {
+        if (mId == null) {
+            throw new IllegalStateException("ID was not set");
+        }
+        return mId;
+    }
+
+    /**
      * Closes the connection for this client and the Context Hub Service.
      *
      * When this function is invoked, the messaging associated with this client is invalidated.
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
index 78cca96..310ebe9 100644
--- a/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorEvent.java
@@ -81,7 +81,7 @@
                     int monitoringType = source.readInt();
                     int monitoringStatus = source.readInt();
                     int sourceTechnologies = source.readInt();
-                    Location location = source.readParcelable(classLoader);
+                    Location location = source.readParcelable(classLoader, android.location.Location.class);
 
                     return new GeofenceHardwareMonitorEvent(
                             monitoringType,
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index e33545c..2423a58 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -29,4 +29,7 @@
 
     // Closes the connection with the Context Hub
     void close();
+
+    // Returns the unique ID for this client.
+    int getId();
 }
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/hardware/location/NanoAppRpcService.aidl
similarity index 80%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/hardware/location/NanoAppRpcService.aidl
index 0abc7ec..72ac8af 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/hardware/location/NanoAppRpcService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.hardware.location;
 
-parcelable TsRequest;
+/**
+ * @hide
+ */
+parcelable NanoAppRpcService;
diff --git a/core/java/android/hardware/location/NanoAppRpcService.java b/core/java/android/hardware/location/NanoAppRpcService.java
new file mode 100644
index 0000000..4b8f058
--- /dev/null
+++ b/core/java/android/hardware/location/NanoAppRpcService.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 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.location;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * An RPC service published by a nanoapp.
+ *
+ * This class is meant to be informational and allows a nanoapp client to know if a given
+ * nanoapp publishes a service which supports an RPC interface. The implementation of the RPC
+ * interface is not specified by the Android APIs and is built by the platform implementation
+ * over the message payloads transferred through the {@link ContextHubClient}.
+ *
+ * This class is instantiated as a part of {@link NanoAppState}, which is provided as a result
+ * of {@link ContextHubManager.queryNanoApps(ContextHubInfo)}.
+ *
+ * See the chrePublishRpcServices() API for how this service is published by the nanoapp.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NanoAppRpcService implements Parcelable {
+    private long mServiceId;
+
+    private int mServiceVersion;
+
+    /**
+     * @param serviceId The unique ID of this service, see {#getId()}.
+     * @param serviceVersion The software version of this service, see {#getVersion()}.
+     */
+    public NanoAppRpcService(long serviceId, int serviceVersion) {
+        mServiceId = serviceId;
+        mServiceVersion = serviceVersion;
+    }
+
+    /**
+     * The unique 64-bit ID of an RPC service published by a nanoapp. Note that
+     * the uniqueness is only required within the nanoapp's domain (i.e. the
+     * combination of the nanoapp ID and service id must be unique).
+     *
+     * This ID must remain the same for the given nanoapp RPC service once
+     * published on Android (i.e. must never change).
+     *
+     * @return The service ID.
+     */
+    public long getId() {
+        return mServiceId;
+    }
+
+    /**
+     * The software version of this service, which follows the sematic
+     * versioning scheme (see semver.org). It follows the format
+     * major.minor.patch, where major and minor versions take up one byte
+     * each, and the patch version takes up the final 2 (lower) bytes.
+     * I.e. the version is encoded as 0xMMmmpppp, where MM, mm, pppp are
+     * the major, minor, patch versions, respectively.
+     *
+     * @return The service version.
+     */
+    public int getVersion() {
+        return mServiceVersion;
+    }
+
+    /**
+     * @return The service's major version.
+     */
+    private int getMajorVersion() {
+        return (mServiceVersion & 0xFF000000) >>> 24;
+    }
+
+    /**
+     * @return The service's minor version.
+     */
+    private int getMinorVersion() {
+        return (mServiceVersion & 0x00FF0000) >>> 16;
+    }
+
+    /**
+     * @return The service's patch version.
+     */
+    private int getPatchVersion() {
+        return mServiceVersion & 0x0000FFFF;
+    }
+
+    private NanoAppRpcService(Parcel in) {
+        mServiceId = in.readLong();
+        mServiceVersion = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeLong(mServiceId);
+        out.writeInt(mServiceVersion);
+    }
+
+    public static final @NonNull Creator<NanoAppRpcService> CREATOR =
+            new Creator<NanoAppRpcService>() {
+                @Override
+                public NanoAppRpcService createFromParcel(Parcel in) {
+                    return new NanoAppRpcService(in);
+                }
+
+                @Override
+                public NanoAppRpcService[] newArray(int size) {
+                    return new NanoAppRpcService[size];
+                }
+            };
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "NanoAppRpcService[Id = " + Long.toHexString(mServiceId) + ", version = v"
+                + getMajorVersion() + "." + getMinorVersion() + "." + getPatchVersion() + "]";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object == this) {
+            return true;
+        }
+
+        boolean isEqual = false;
+        if (object instanceof NanoAppRpcService) {
+            NanoAppRpcService other = (NanoAppRpcService) object;
+            isEqual = (other.getId() == mServiceId)
+                    && (other.getVersion() == mServiceVersion);
+        }
+
+        return isEqual;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) getId();
+    }
+}
diff --git a/core/java/android/hardware/location/NanoAppState.java b/core/java/android/hardware/location/NanoAppState.java
index 96b1f19..b649054 100644
--- a/core/java/android/hardware/location/NanoAppState.java
+++ b/core/java/android/hardware/location/NanoAppState.java
@@ -21,10 +21,16 @@
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 /**
- * A class describing the nanoapp state information resulting from a query to a Context Hub.
+ * A class describing the nanoapp state information resulting from a query to a Context Hub
+ * through {@link ContextHubManager.queryNanoApps(ContextHubInfo)}. It contains metadata about
+ * the nanoapp running on a Context Hub.
+ *
+ * See "struct chreNanoappInfo" in the CHRE API (system/chre/chre_api) for additional details.
  *
  * @hide
  */
@@ -33,31 +39,70 @@
     private long mNanoAppId;
     private int mNanoAppVersion;
     private boolean mIsEnabled;
-    private List<String> mNanoAppPermissions;
+    private List<String> mNanoAppPermissions = new ArrayList<String>();
+    private List<NanoAppRpcService> mNanoAppRpcServiceList =
+                new ArrayList<NanoAppRpcService>();
 
+    /**
+     * @param nanoAppId The unique ID of this nanoapp, see {#getNanoAppId()}.
+     * @param appVersion The software version of this nanoapp, see {#getNanoAppVersion()}.
+     * @param enabled True if the nanoapp is enabled and running on the Context Hub.
+     */
     public NanoAppState(long nanoAppId, int appVersion, boolean enabled) {
         mNanoAppId = nanoAppId;
         mNanoAppVersion = appVersion;
         mIsEnabled = enabled;
-        mNanoAppPermissions = new ArrayList<String>();
     }
 
+    /**
+     * @param nanoAppId The unique ID of this nanoapp, see {#getNanoAppId()}.
+     * @param appVersion The software version of this nanoapp, see {#getNanoAppVersion()}.
+     * @param enabled True if the nanoapp is enabled and running on the Context Hub.
+     * @param nanoAppPermissions The list of permissions required to communicate with this
+     *   nanoapp.
+     */
     public NanoAppState(long nanoAppId, int appVersion, boolean enabled,
                         @NonNull List<String> nanoAppPermissions) {
         mNanoAppId = nanoAppId;
         mNanoAppVersion = appVersion;
         mIsEnabled = enabled;
-        mNanoAppPermissions = nanoAppPermissions;
+        mNanoAppPermissions = Collections.unmodifiableList(nanoAppPermissions);
     }
 
     /**
-     * @return the NanoAppInfo for this app
+     * @param nanoAppId The unique ID of this nanoapp, see {#getNanoAppId()}.
+     * @param appVersion The software version of this nanoapp, see {#getNanoAppVersion()}.
+     * @param enabled True if the nanoapp is enabled and running on the Context Hub.
+     * @param nanoAppPermissions The list of permissions required to communicate with this
+     *   nanoapp.
+     * @param nanoAppRpcServiceList The list of RPC services published by this nanoapp, see
+     *   {@link NanoAppRpcService} for additional details.
+     */
+    public NanoAppState(long nanoAppId, int appVersion, boolean enabled,
+                        @NonNull List<String> nanoAppPermissions,
+                        @NonNull List<NanoAppRpcService> nanoAppRpcServiceList) {
+        mNanoAppId = nanoAppId;
+        mNanoAppVersion = appVersion;
+        mIsEnabled = enabled;
+        mNanoAppPermissions = Collections.unmodifiableList(nanoAppPermissions);
+        mNanoAppRpcServiceList = Collections.unmodifiableList(nanoAppRpcServiceList);
+    }
+
+    /**
+     * @return the unique ID of this nanoapp, which must never change once released on Android.
      */
     public long getNanoAppId() {
         return mNanoAppId;
     }
 
     /**
+     * The software version of this service, which follows the sematic
+     * versioning scheme (see semver.org). It follows the format
+     * major.minor.patch, where major and minor versions take up one byte
+     * each, and the patch version takes up the final 2 (lower) bytes.
+     * I.e. the version is encoded as 0xMMmmpppp, where MM, mm, pppp are
+     * the major, minor, patch versions, respectively.
+     *
      * @return the app version
      */
     public long getNanoAppVersion() {
@@ -72,18 +117,29 @@
     }
 
     /**
-     * @return List of Android permissions that are required to communicate with this app.
+     * @return A read-only list of Android permissions that are all required to communicate with
+     *   this nanoapp.
      */
     public @NonNull List<String> getNanoAppPermissions() {
         return mNanoAppPermissions;
     }
 
+    /**
+     * @return A read-only list of RPC services supported by this nanoapp.
+     */
+    public @NonNull List<NanoAppRpcService> getRpcServices() {
+        return mNanoAppRpcServiceList;
+    }
+
     private NanoAppState(Parcel in) {
         mNanoAppId = in.readLong();
         mNanoAppVersion = in.readInt();
         mIsEnabled = (in.readInt() == 1);
         mNanoAppPermissions = new ArrayList<String>();
         in.readStringList(mNanoAppPermissions);
+        mNanoAppRpcServiceList = Collections.unmodifiableList(
+                    Arrays.asList(in.readParcelableArray(
+                    NanoAppRpcService.class.getClassLoader(), NanoAppRpcService.class)));
     }
 
     @Override
@@ -97,6 +153,7 @@
         out.writeInt(mNanoAppVersion);
         out.writeInt(mIsEnabled ? 1 : 0);
         out.writeStringList(mNanoAppPermissions);
+        out.writeParcelableArray(mNanoAppRpcServiceList.toArray(new NanoAppRpcService[0]), 0);
     }
 
     public static final @android.annotation.NonNull Creator<NanoAppState> CREATOR =
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index 37425ff..1c4089c 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -160,6 +160,7 @@
         }
     }
 
+    @SuppressWarnings("UnsafeParcelApi")
     public static final @android.annotation.NonNull Creator<InterfaceConfiguration> CREATOR = new Creator<
             InterfaceConfiguration>() {
         public InterfaceConfiguration createFromParcel(Parcel in) {
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 8a0211c..f33a035 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -16,11 +16,19 @@
 
 package android.net;
 
+import static android.net.NetworkStats.METERED_ALL;
+import static android.net.NetworkStats.METERED_YES;
+import static android.net.NetworkTemplate.MATCH_CARRIER;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.SUBSCRIBER_ID_MATCH_RULE_EXACT;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.BackupUtils;
+import android.util.Log;
 import android.util.Range;
 import android.util.RecurrenceRule;
 
@@ -42,10 +50,25 @@
  * @hide
  */
 public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> {
+    private static final String TAG = NetworkPolicy.class.getSimpleName();
     private static final int VERSION_INIT = 1;
     private static final int VERSION_RULE = 2;
     private static final int VERSION_RAPID = 3;
 
+    /**
+     * Initial Version of the NetworkTemplate backup serializer.
+     */
+    private static final int TEMPLATE_BACKUP_VERSION_1_INIT = 1;
+    /**
+     * Version of the NetworkTemplate backup serializer that added carrier template support.
+     */
+    private static final int TEMPLATE_BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2;
+    /**
+     * Latest Version of the NetworkTemplate Backup Serializer.
+     */
+    private static final int TEMPLATE_BACKUP_VERSION_LATEST =
+            TEMPLATE_BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE;
+
     public static final int CYCLE_NONE = -1;
     public static final long WARNING_DISABLED = -1;
     public static final long LIMIT_DISABLED = -1;
@@ -115,8 +138,8 @@
     }
 
     private NetworkPolicy(Parcel source) {
-        template = source.readParcelable(null);
-        cycleRule = source.readParcelable(null);
+        template = source.readParcelable(null, android.net.NetworkTemplate.class);
+        cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class);
         warningBytes = source.readLong();
         limitBytes = source.readLong();
         lastWarningSnooze = source.readLong();
@@ -255,7 +278,7 @@
         DataOutputStream out = new DataOutputStream(baos);
 
         out.writeInt(VERSION_RAPID);
-        out.write(template.getBytesForBackup());
+        out.write(getNetworkTemplateBytesForBackup());
         cycleRule.writeToStream(out);
         out.writeLong(warningBytes);
         out.writeLong(limitBytes);
@@ -274,7 +297,7 @@
             throw new BackupUtils.BadVersionException("Unknown backup version: " + version);
         }
 
-        final NetworkTemplate template = NetworkTemplate.getNetworkTemplateFromBackup(in);
+        final NetworkTemplate template = getNetworkTemplateFromBackup(in);
         final RecurrenceRule cycleRule;
         if (version >= VERSION_RULE) {
             cycleRule = new RecurrenceRule(in);
@@ -298,4 +321,61 @@
         return new NetworkPolicy(template, cycleRule, warningBytes, limitBytes, lastWarningSnooze,
                 lastLimitSnooze, lastRapidSnooze, metered, inferred);
     }
+
+    @NonNull
+    private byte[] getNetworkTemplateBytesForBackup() throws IOException {
+        if (!template.isPersistable()) {
+            Log.wtf(TAG, "Trying to backup non-persistable template: " + this);
+        }
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        final DataOutputStream out = new DataOutputStream(baos);
+
+        out.writeInt(TEMPLATE_BACKUP_VERSION_LATEST);
+
+        out.writeInt(template.getMatchRule());
+        BackupUtils.writeString(out, template.getSubscriberId());
+        BackupUtils.writeString(out, template.getNetworkId());
+        out.writeInt(template.getMeteredness());
+        out.writeInt(template.getSubscriberIdMatchRule());
+
+        return baos.toByteArray();
+    }
+
+    @NonNull
+    private static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
+            throws IOException, BackupUtils.BadVersionException {
+        int version = in.readInt();
+        if (version < TEMPLATE_BACKUP_VERSION_1_INIT || version > TEMPLATE_BACKUP_VERSION_LATEST) {
+            throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
+        }
+
+        int matchRule = in.readInt();
+        final String subscriberId = BackupUtils.readString(in);
+        final String networkId = BackupUtils.readString(in);
+
+        final int metered;
+        final int subscriberIdMatchRule;
+        if (version >= TEMPLATE_BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) {
+            metered = in.readInt();
+            subscriberIdMatchRule = in.readInt();
+        } else {
+            // For backward compatibility, fill the missing filters from match rules.
+            metered = (matchRule == MATCH_MOBILE
+                    || matchRule == NetworkTemplate.MATCH_MOBILE_WILDCARD
+                    || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL;
+            subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT;
+        }
+
+        try {
+            return new NetworkTemplate(matchRule,
+                    subscriberId, new String[]{subscriberId},
+                    networkId, metered, NetworkStats.ROAMING_ALL,
+                    NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
+                    NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
+        } catch (IllegalArgumentException e) {
+            throw new BackupUtils.BadVersionException(
+                    "Restored network template contains unknown match rule " + matchRule, e);
+        }
+    }
 }
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 7b8b5c0..8046629 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -52,7 +52,7 @@
  * </ul>
  *
  * @deprecated No longer functional on {@link android.os.Build.VERSION_CODES#TIRAMISU} and above.
- * See https://developer.android.com/guide/topics/connectivity/wifi-suggest for
+ * See <a href="{@docRoot}guide/topics/connectivity/wifi-suggest">Wi-Fi Suggestion API</a> for
  * alternative APIs to suggest/configure Wi-Fi networks.
  * @hide
  */
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index f55bcd3..b989488 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -2,5 +2,6 @@
 
 include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
 
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
 per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java
index 2e4ea89..a67827e 100644
--- a/core/java/android/net/WifiKey.java
+++ b/core/java/android/net/WifiKey.java
@@ -29,7 +29,7 @@
  * Information identifying a Wi-Fi network.
  * @see NetworkKey
  *
- * @deprecated as part of the {@link NetworkScore} deprecation.
+ * @deprecated as part of the {@link NetworkScoreManager} deprecation.
  * @hide
  */
 @Deprecated
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
index 6b33e4f..b3f7345 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
@@ -29,6 +29,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import java.util.ArrayList;
@@ -150,7 +151,7 @@
 
     /** Retrieve the allowed PLMN IDs, or an empty set if any PLMN ID is acceptable. */
     @NonNull
-    public Set<String> getAllowedPlmnIds() {
+    public Set<String> getAllowedOperatorPlmnIds() {
         return Collections.unmodifiableSet(mAllowedNetworkPlmnIds);
     }
 
@@ -200,8 +201,17 @@
                 && mRequireOpportunistic == rhs.mRequireOpportunistic;
     }
 
+    /** @hide */
+    @Override
+    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+        pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
+        pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
+        pw.println("mAllowRoaming: " + mAllowRoaming);
+        pw.println("mRequireOpportunistic: " + mRequireOpportunistic);
+    }
+
     /** This class is used to incrementally build WifiNetworkPriority objects. */
-    public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
+    public static final class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
         @NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
         @NonNull private final Set<Integer> mAllowedSpecificCarrierIds = new ArraySet<>();
 
@@ -223,7 +233,7 @@
          *     and {@link SubscriptionInfo#getMncString()}.
          */
         @NonNull
-        public Builder setAllowedPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
+        public Builder setAllowedOperatorPlmnIds(@NonNull Set<String> allowedNetworkPlmnIds) {
             validatePlmnIds(allowedNetworkPlmnIds);
 
             mAllowedNetworkPlmnIds.clear();
diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java
index caab152..fd3fe37 100644
--- a/core/java/android/net/vcn/VcnConfig.java
+++ b/core/java/android/net/vcn/VcnConfig.java
@@ -173,7 +173,7 @@
             new Parcelable.Creator<VcnConfig>() {
                 @NonNull
                 public VcnConfig createFromParcel(Parcel in) {
-                    return new VcnConfig((PersistableBundle) in.readParcelable(null));
+                    return new VcnConfig((PersistableBundle) in.readParcelable(null, android.os.PersistableBundle.class));
                 }
 
                 @NonNull
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index de4ada2..55d3ecd 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -160,7 +160,9 @@
                 TimeUnit.MINUTES.toMillis(15)
             };
 
-    private static final LinkedHashSet<VcnUnderlyingNetworkPriority>
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final LinkedHashSet<VcnUnderlyingNetworkPriority>
             DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
 
     static {
@@ -196,7 +198,10 @@
     private static final String EXPOSED_CAPABILITIES_KEY = "mExposedCapabilities";
     @NonNull private final SortedSet<Integer> mExposedCapabilities;
 
-    private static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+    /** @hide */
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static final String UNDERLYING_NETWORK_PRIORITIES_KEY = "mUnderlyingNetworkPriorities";
+
     @NonNull private final LinkedHashSet<VcnUnderlyingNetworkPriority> mUnderlyingNetworkPriorities;
 
     private static final String MAX_MTU_KEY = "mMaxMtu";
@@ -227,6 +232,8 @@
         validate();
     }
 
+    // Null check MUST be done for all new fields added to VcnGatewayConnectionConfig, to avoid
+    // crashes when parsing PersistableBundle built on old platforms.
     /** @hide */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     public VcnGatewayConnectionConfig(@NonNull PersistableBundle in) {
@@ -237,19 +244,30 @@
 
         final PersistableBundle exposedCapsBundle =
                 in.getPersistableBundle(EXPOSED_CAPABILITIES_KEY);
-        final PersistableBundle networkPrioritiesBundle =
-                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
-
         mGatewayConnectionName = in.getString(GATEWAY_CONNECTION_NAME_KEY);
         mTunnelConnectionParams =
                 TunnelConnectionParamsUtils.fromPersistableBundle(tunnelConnectionParamsBundle);
         mExposedCapabilities = new TreeSet<>(PersistableBundleUtils.toList(
                 exposedCapsBundle, PersistableBundleUtils.INTEGER_DESERIALIZER));
-        mUnderlyingNetworkPriorities =
-                new LinkedHashSet<>(
-                        PersistableBundleUtils.toList(
-                                networkPrioritiesBundle,
-                                VcnUnderlyingNetworkPriority::fromPersistableBundle));
+
+        final PersistableBundle networkPrioritiesBundle =
+                in.getPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY);
+
+        if (networkPrioritiesBundle == null) {
+            // UNDERLYING_NETWORK_PRIORITIES_KEY was added in Android T. Thus
+            // VcnGatewayConnectionConfig created on old platforms will not have this data and will
+            // be assigned with the default value
+            mUnderlyingNetworkPriorities =
+                    new LinkedHashSet<>(DEFAULT_UNDERLYING_NETWORK_PRIORITIES);
+
+        } else {
+            mUnderlyingNetworkPriorities =
+                    new LinkedHashSet<>(
+                            PersistableBundleUtils.toList(
+                                    networkPrioritiesBundle,
+                                    VcnUnderlyingNetworkPriority::fromPersistableBundle));
+        }
+
         mRetryIntervalsMs = in.getLongArray(RETRY_INTERVAL_MS_KEY);
         mMaxMtu = in.getInt(MAX_MTU_KEY);
 
@@ -418,6 +436,7 @@
                 mGatewayConnectionName,
                 mTunnelConnectionParams,
                 mExposedCapabilities,
+                mUnderlyingNetworkPriorities,
                 Arrays.hashCode(mRetryIntervalsMs),
                 mMaxMtu);
     }
@@ -432,6 +451,7 @@
         return mGatewayConnectionName.equals(rhs.mGatewayConnectionName)
                 && mTunnelConnectionParams.equals(rhs.mTunnelConnectionParams)
                 && mExposedCapabilities.equals(rhs.mExposedCapabilities)
+                && mUnderlyingNetworkPriorities.equals(rhs.mUnderlyingNetworkPriorities)
                 && Arrays.equals(mRetryIntervalsMs, rhs.mRetryIntervalsMs)
                 && mMaxMtu == rhs.mMaxMtu;
     }
diff --git a/core/java/android/net/vcn/VcnNetworkPolicyResult.java b/core/java/android/net/vcn/VcnNetworkPolicyResult.java
index 14e70cf..fca084a 100644
--- a/core/java/android/net/vcn/VcnNetworkPolicyResult.java
+++ b/core/java/android/net/vcn/VcnNetworkPolicyResult.java
@@ -114,7 +114,7 @@
     public static final @NonNull Creator<VcnNetworkPolicyResult> CREATOR =
             new Creator<VcnNetworkPolicyResult>() {
                 public VcnNetworkPolicyResult createFromParcel(Parcel in) {
-                    return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null));
+                    return new VcnNetworkPolicyResult(in.readBoolean(), in.readParcelable(null, android.net.NetworkCapabilities.class));
                 }
 
                 public VcnNetworkPolicyResult[] newArray(int size) {
diff --git a/core/java/android/net/vcn/VcnTransportInfo.java b/core/java/android/net/vcn/VcnTransportInfo.java
index 25a2574..5c47b28 100644
--- a/core/java/android/net/vcn/VcnTransportInfo.java
+++ b/core/java/android/net/vcn/VcnTransportInfo.java
@@ -146,7 +146,7 @@
             new Creator<VcnTransportInfo>() {
                 public VcnTransportInfo createFromParcel(Parcel in) {
                     final int subId = in.readInt();
-                    final WifiInfo wifiInfo = in.readParcelable(null);
+                    final WifiInfo wifiInfo = in.readParcelable(null, android.net.wifi.WifiInfo.class);
 
                     // If all fields are their null values, return null TransportInfo to avoid
                     // leaking information about this being a VCN Network (instead of macro
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
index b0d4f3b..2b5305d 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPolicy.java
@@ -106,7 +106,7 @@
     public static final @NonNull Creator<VcnUnderlyingNetworkPolicy> CREATOR =
             new Creator<VcnUnderlyingNetworkPolicy>() {
                 public VcnUnderlyingNetworkPolicy createFromParcel(Parcel in) {
-                    return new VcnUnderlyingNetworkPolicy(in.readParcelable(null));
+                    return new VcnUnderlyingNetworkPolicy(in.readParcelable(null, android.net.vcn.VcnNetworkPolicyResult.class));
                 }
 
                 public VcnUnderlyingNetworkPolicy[] newArray(int size) {
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
index 82f6ae7..551f757 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
@@ -21,8 +21,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.PersistableBundle;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -37,10 +39,17 @@
     /** @hide */
     protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
 
-    /** Denotes that network quality needs to be OK */
-    public static final int NETWORK_QUALITY_OK = 10000;
     /** Denotes that any network quality is acceptable */
-    public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE;
+    public static final int NETWORK_QUALITY_ANY = 0;
+    /** Denotes that network quality needs to be OK */
+    public static final int NETWORK_QUALITY_OK = 100000;
+
+    private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
+
+    static {
+        NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY");
+        NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK");
+    }
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -125,6 +134,28 @@
                 && mAllowMetered == rhs.mAllowMetered;
     }
 
+    /** @hide */
+    abstract void dumpTransportSpecificFields(IndentingPrintWriter pw);
+
+    /**
+     * Dumps the state of this record for logging and debugging purposes.
+     *
+     * @hide
+     */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println(this.getClass().getSimpleName() + ":");
+        pw.increaseIndent();
+
+        pw.println(
+                "mNetworkQuality: "
+                        + NETWORK_QUALITY_TO_STRING_MAP.get(
+                                mNetworkQuality, "Invalid value " + mNetworkQuality));
+        pw.println("mAllowMetered: " + mAllowMetered);
+        dumpTransportSpecificFields(pw);
+
+        pw.decreaseIndent();
+    }
+
     /** Retrieve the required network quality. */
     @NetworkQuality
     public int getNetworkQuality() {
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
index fc7e7e2..85eb100 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -22,6 +22,7 @@
 import android.os.PersistableBundle;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.Objects;
 
@@ -78,7 +79,13 @@
         }
 
         final VcnWifiUnderlyingNetworkPriority rhs = (VcnWifiUnderlyingNetworkPriority) other;
-        return mSsid == rhs.mSsid;
+        return mSsid.equals(rhs.mSsid);
+    }
+
+    /** @hide */
+    @Override
+    void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+        pw.println("mSsid: " + mSsid);
     }
 
     /** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
index ed3b74a..6a40f98 100644
--- a/core/java/android/nfc/BeamShareData.java
+++ b/core/java/android/nfc/BeamShareData.java
@@ -47,13 +47,13 @@
         @Override
         public BeamShareData createFromParcel(Parcel source) {
             Uri[] uris = null;
-            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader());
+            NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader(), android.nfc.NdefMessage.class);
             int numUris = source.readInt();
             if (numUris > 0) {
                 uris = new Uri[numUris];
                 source.readTypedArray(uris, Uri.CREATOR);
             }
-            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader());
+            UserHandle userHandle = source.readParcelable(UserHandle.class.getClassLoader(), android.os.UserHandle.class);
             int flags = source.readInt();
 
             return new BeamShareData(msg, uris, userHandle, flags);
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 74b814e..c8b4226e 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -45,6 +45,8 @@
     // Last UID/GID of the range the AppZygote can setuid()/setgid() to
     private final int mZygoteUidGidMax;
 
+    private final int mZygoteRuntimeFlags;
+
     private final Object mLock = new Object();
 
     /**
@@ -56,11 +58,13 @@
 
     private final ApplicationInfo mAppInfo;
 
-    public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax) {
+    public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax,
+            int runtimeFlags) {
         mAppInfo = appInfo;
         mZygoteUid = zygoteUid;
         mZygoteUidGidMin = uidGidMin;
         mZygoteUidGidMax = uidGidMax;
+        mZygoteRuntimeFlags = runtimeFlags;
     }
 
     /**
@@ -110,7 +114,7 @@
                     mZygoteUid,
                     mZygoteUid,
                     null,  // gids
-                    0,  // runtimeFlags
+                    mZygoteRuntimeFlags,  // runtimeFlags
                     "app_zygote",  // seInfo
                     abi,  // abi
                     abi, // acceptedAbiList
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index ba9332d..c607195 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -161,6 +161,7 @@
      */
     @IntDef(prefix = {"PROCESS_STATE_"}, value = {
             PROCESS_STATE_ANY,
+            PROCESS_STATE_UNSPECIFIED,
             PROCESS_STATE_FOREGROUND,
             PROCESS_STATE_BACKGROUND,
             PROCESS_STATE_FOREGROUND_SERVICE,
@@ -169,7 +170,8 @@
     public @interface ProcessState {
     }
 
-    public static final int PROCESS_STATE_ANY = 0;
+    public static final int PROCESS_STATE_UNSPECIFIED = 0;
+    public static final int PROCESS_STATE_ANY = PROCESS_STATE_UNSPECIFIED;
     public static final int PROCESS_STATE_FOREGROUND = 1;
     public static final int PROCESS_STATE_BACKGROUND = 2;
     public static final int PROCESS_STATE_FOREGROUND_SERVICE = 3;
@@ -180,7 +182,7 @@
 
     static {
         // Assign individually to avoid future mismatch
-        sProcessStateNames[PROCESS_STATE_ANY] = "any";
+        sProcessStateNames[PROCESS_STATE_UNSPECIFIED] = "unspecified";
         sProcessStateNames[PROCESS_STATE_FOREGROUND] = "fg";
         sProcessStateNames[PROCESS_STATE_BACKGROUND] = "bg";
         sProcessStateNames[PROCESS_STATE_FOREGROUND_SERVICE] = "fgs";
@@ -188,6 +190,7 @@
 
     private static final int[] SUPPORTED_POWER_COMPONENTS_PER_PROCESS_STATE = {
             POWER_COMPONENT_CPU,
+            POWER_COMPONENT_MOBILE_RADIO,
     };
 
     static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
@@ -213,7 +216,7 @@
                 sb.append("powerComponent=").append(sPowerComponentNames[powerComponent]);
                 dimensionSpecified = true;
             }
-            if (processState != PROCESS_STATE_ANY) {
+            if (processState != PROCESS_STATE_UNSPECIFIED) {
                 if (dimensionSpecified) {
                     sb.append(", ");
                 }
@@ -283,7 +286,7 @@
             if (mShortString == null) {
                 StringBuilder sb = new StringBuilder();
                 sb.append(powerComponentIdToString(powerComponent));
-                if (processState != PROCESS_STATE_ANY) {
+                if (processState != PROCESS_STATE_UNSPECIFIED) {
                     sb.append(':');
                     sb.append(processStateToString(processState));
                 }
@@ -333,7 +336,7 @@
      * for all values of other dimensions such as process state.
      */
     public Key getKey(@PowerComponent int componentId) {
-        return mData.getKey(componentId, PROCESS_STATE_ANY);
+        return mData.getKey(componentId, PROCESS_STATE_UNSPECIFIED);
     }
 
     /**
@@ -352,7 +355,7 @@
      */
     public double getConsumedPower(@PowerComponent int componentId) {
         return mPowerComponents.getConsumedPower(
-                mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY));
+                mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
     }
 
     /**
@@ -374,7 +377,7 @@
      */
     public @PowerModel int getPowerModel(@BatteryConsumer.PowerComponent int componentId) {
         return mPowerComponents.getPowerModel(
-                mData.getKeyOrThrow(componentId, PROCESS_STATE_ANY));
+                mData.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED));
     }
 
     /**
@@ -706,7 +709,7 @@
                     if (isSupported) {
                         for (int processState = 0; processState < PROCESS_STATE_COUNT;
                                 processState++) {
-                            if (processState == PROCESS_STATE_ANY) {
+                            if (processState == PROCESS_STATE_UNSPECIFIED) {
                                 continue;
                             }
 
@@ -789,7 +792,7 @@
         @NonNull
         public T setConsumedPower(@PowerComponent int componentId, double componentPower,
                 @PowerModel int powerModel) {
-            mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_ANY),
+            mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
                     componentPower, powerModel);
             return (T) this;
         }
@@ -825,8 +828,9 @@
         @NonNull
         public T setUsageDurationMillis(@UidBatteryConsumer.PowerComponent int componentId,
                 long componentUsageTimeMillis) {
-            mPowerComponentsBuilder.setUsageDurationMillis(getKey(componentId, PROCESS_STATE_ANY),
-                    componentUsageTimeMillis);
+            mPowerComponentsBuilder
+                    .setUsageDurationMillis(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
+                            componentUsageTimeMillis);
             return (T) this;
         }
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 53484d2..fa209cc 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -634,7 +634,7 @@
      */
     public static int mapToInternalProcessState(int procState) {
         if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
-            return ActivityManager.PROCESS_STATE_NONEXISTENT;
+            return Uid.PROCESS_STATE_NONEXISTENT;
         } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
             return Uid.PROCESS_STATE_TOP;
         } else if (ActivityManager.isForegroundService(procState)) {
@@ -669,7 +669,7 @@
             case BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE:
                 return BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
             default:
-                return BatteryConsumer.PROCESS_STATE_ANY;
+                return BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
         }
     }
 
@@ -911,6 +911,11 @@
          * Total number of process states we track.
          */
         public static final int NUM_PROCESS_STATE = 7;
+        /**
+         * State of the UID when it has no running processes.  It is intentionally out of
+         * bounds 0..NUM_PROCESS_STATE.
+         */
+        public static final int PROCESS_STATE_NONEXISTENT = NUM_PROCESS_STATE;
 
         // Used in dump
         static final String[] PROCESS_STATE_NAMES = {
@@ -930,16 +935,6 @@
                 "C"   // CACHED
         };
 
-        /**
-         * When the process exits one of these states, we need to make sure cpu time in this state
-         * is not attributed to any non-critical process states.
-         */
-        public static final int[] CRITICAL_PROC_STATES = {
-                Uid.PROCESS_STATE_TOP,
-                Uid.PROCESS_STATE_FOREGROUND_SERVICE,
-                Uid.PROCESS_STATE_FOREGROUND
-        };
-
         public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
         public abstract Timer getProcessStateTimer(int state);
 
@@ -968,6 +963,13 @@
         public abstract long getNetworkActivityPackets(int type, int which);
         @UnsupportedAppUsage
         public abstract long getMobileRadioActiveTime(int which);
+
+        /**
+         * Returns the amount of time (in microseconds) this UID was in the specified processState.
+         */
+        public abstract long getMobileRadioActiveTimeInProcessState(
+                @BatteryConsumer.ProcessState int processState);
+
         public abstract int getMobileRadioActiveCount(int which);
 
         /**
@@ -1066,6 +1068,16 @@
         public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
 
         /**
+         * Returns the battery consumption (in microcoulombs) of the uid's radio usage when in the
+         * specified process state.
+         * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
+         *
+         * {@hide}
+         */
+        public abstract long getMobileRadioMeasuredBatteryConsumptionUC(
+                @BatteryConsumer.ProcessState int processState);
+
+        /**
          * Returns the battery consumption (in microcoulombs) of the screen while on and uid active,
          * derived from on device power measurement data.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index ed44fb6..a23dae8 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -556,7 +556,7 @@
                 }
 
                 String label = BatteryConsumer.powerComponentIdToString(componentId);
-                if (key.processState != BatteryConsumer.PROCESS_STATE_ANY) {
+                if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                     label = label
                             + "(" + BatteryConsumer.processStateToString(key.processState) + ")";
                 }
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index b069fb3..429450c 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -22,9 +22,11 @@
 import android.app.AppOpsManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.util.ExceptionUtils;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BinderCallHeavyHitterWatcher;
 import com.android.internal.os.BinderCallHeavyHitterWatcher.BinderCallHeavyHitterListener;
 import com.android.internal.os.BinderInternal;
@@ -140,33 +142,55 @@
     /**
      * Flag indicating whether we should be tracing transact calls.
      */
-    private static volatile boolean sTracingEnabled = false;
+    private static volatile boolean sStackTrackingEnabled = false;
+
+    private static final Object sTracingUidsWriteLock = new Object();
+    private static volatile IntArray sTracingUidsImmutable = new IntArray();
 
     /**
-     * Enable Binder IPC tracing.
+     * Enable Binder IPC stack tracking. If enabled, every binder transaction will be logged to
+     * {@link TransactionTracker}.
      *
      * @hide
      */
-    public static void enableTracing() {
-        sTracingEnabled = true;
+    public static void enableStackTracking() {
+        sStackTrackingEnabled = true;
     }
 
     /**
-     * Disable Binder IPC tracing.
+     * Disable Binder IPC stack tracking.
      *
      * @hide
      */
-    public static void disableTracing() {
-        sTracingEnabled = false;
+    public static void disableStackTracking() {
+        sStackTrackingEnabled = false;
     }
 
     /**
-     * Check if binder transaction tracing is enabled.
+     * @hide
+     */
+    public static void enableTracingForUid(int uid) {
+        synchronized (sTracingUidsWriteLock) {
+            final IntArray copy = sTracingUidsImmutable.clone();
+            copy.add(uid);
+            sTracingUidsImmutable = copy;
+        }
+    }
+
+    /**
+     * Check if binder transaction stack tracking is enabled.
      *
      * @hide
      */
-    public static boolean isTracingEnabled() {
-        return sTracingEnabled;
+    public static boolean isStackTrackingEnabled() {
+        return sStackTrackingEnabled;
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean isTracingEnabled(int callingUid) {
+        return sTracingUidsImmutable.indexOf(callingUid) != -1;
     }
 
     /**
@@ -288,6 +312,9 @@
 
     private IInterface mOwner;
     private String mDescriptor;
+    private volatile String[] mTransactionTraceNames = null;
+    private volatile String mSimpleDescriptor = null;
+    private static final int TRANSACTION_TRACE_NAME_ID_LIMIT = 1024;
 
     /**
      * Return the ID of the process that sent you the current transaction
@@ -884,6 +911,53 @@
     }
 
     /**
+     * @hide
+     */
+    @VisibleForTesting
+    public final @NonNull String getTransactionTraceName(int transactionCode) {
+        if (mTransactionTraceNames == null) {
+            final String descriptor = getSimpleDescriptor();
+            final int highestId = Math.min(getMaxTransactionId(), TRANSACTION_TRACE_NAME_ID_LIMIT);
+            final String[] transactionNames = new String[highestId + 1];
+            final StringBuffer buf = new StringBuffer();
+            for (int i = 0; i <= highestId; i++) {
+                String transactionName = getTransactionName(i + FIRST_CALL_TRANSACTION);
+                if (transactionName != null) {
+                    buf.append(descriptor).append(':').append(transactionName);
+                } else {
+                    buf.append(descriptor).append('#').append(i + FIRST_CALL_TRANSACTION);
+                }
+                transactionNames[i] = buf.toString();
+                buf.setLength(0);
+            }
+            mTransactionTraceNames = transactionNames;
+            mSimpleDescriptor = descriptor;
+        }
+        final int index = transactionCode - FIRST_CALL_TRANSACTION;
+        if (index < 0 || index >= mTransactionTraceNames.length) {
+            return mSimpleDescriptor + "#" + transactionCode;
+        }
+        return mTransactionTraceNames[index];
+    }
+
+    private String getSimpleDescriptor() {
+        final int dot = mDescriptor.lastIndexOf(".");
+        if (dot > 0) {
+            // Strip the package name
+            return mDescriptor.substring(dot + 1);
+        }
+        return mDescriptor;
+    }
+
+    /**
+     * @return The highest user-defined transaction id of all transactions.
+     * @hide
+     */
+    public int getMaxTransactionId() {
+        return 0;
+    }
+
+    /**
      * Implemented to call the more convenient version
      * {@link #dump(FileDescriptor, PrintWriter, String[])}.
      */
@@ -1181,7 +1255,8 @@
         // Log any exceptions as warnings, don't silently suppress them.
         // If the call was {@link IBinder#FLAG_ONEWAY} then these exceptions
         // disappear into the ether.
-        final boolean tracingEnabled = Binder.isTracingEnabled();
+        final boolean tracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_AIDL) &&
+                (Binder.isStackTrackingEnabled() || Binder.isTracingEnabled(callingUid));
         try {
             final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
             if (heavyHitterWatcher != null) {
@@ -1189,9 +1264,7 @@
                 heavyHitterWatcher.onTransaction(callingUid, getClass(), code);
             }
             if (tracingEnabled) {
-                final String transactionName = getTransactionName(code);
-                Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":"
-                        + (transactionName != null ? transactionName : code));
+                Trace.traceBegin(Trace.TRACE_TAG_AIDL, getTransactionTraceName(code));
             }
 
             if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
@@ -1226,7 +1299,7 @@
             res = true;
         } finally {
             if (tracingEnabled) {
-                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
+                Trace.traceEnd(Trace.TRACE_TAG_AIDL);
             }
             if (observer != null) {
                 // The parcel RPC headers have been called during onTransact so we can now access
@@ -1235,10 +1308,11 @@
                         data.readCallingWorkSourceUid());
                 observer.callEnded(callSession, data.dataSize(), reply.dataSize(), workSourceUid);
             }
+
+            checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
+            reply.recycle();
+            data.recycle();
         }
-        checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
-        reply.recycle();
-        data.recycle();
 
         // Just in case -- we are done with the IPC, so there should be no more strict
         // mode violations that have gathered for this thread. Either they have been
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 483b549..e929920 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -545,7 +545,7 @@
             }
         }
 
-        final boolean tracingEnabled = Binder.isTracingEnabled();
+        final boolean tracingEnabled = Binder.isStackTrackingEnabled();
         if (tracingEnabled) {
             final Throwable tr = new Throwable();
             Binder.getTransactionTracker().addTrace(tr);
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index b3416e9..aa4b83a 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -67,6 +67,9 @@
     private static final String SYSTEM_DRIVER_NAME = "system";
     private static final String SYSTEM_DRIVER_VERSION_NAME = "";
     private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+    private static final String ANGLE_DRIVER_NAME = "angle";
+    private static final String ANGLE_DRIVER_VERSION_NAME = "";
+    private static final long ANGLE_DRIVER_VERSION_CODE = 0;
 
     // System properties related to updatable graphics drivers.
     private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
@@ -134,14 +137,24 @@
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
-        setupAngle(context, coreSettings, pm, packageName);
+        boolean useAngle = false;
+        if (setupAngle(context, coreSettings, pm, packageName)) {
+            if (shouldUseAngle(context, coreSettings, packageName)) {
+                useAngle = true;
+                setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+                        0, packageName, getVulkanVersion(pm));
+            }
+        }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
         if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
-            setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
-                    SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName,
-                    getVulkanVersion(pm));
+            if (!useAngle) {
+                setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
+                        SYSTEM_DRIVER_VERSION_CODE,
+                        SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
+                        packageName, getVulkanVersion(pm));
+            }
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
     }
@@ -179,14 +192,15 @@
 
         // We only want to use ANGLE if the developer has explicitly chosen something other than
         // default driver.
-        final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
-        if (requested) {
+        final boolean forceAngle = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
+        final boolean forceNative = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE);
+        if (forceAngle || forceNative) {
             Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
         }
 
         final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
 
-        return requested || gameModeEnabledAngle;
+        return !forceNative && (forceAngle || gameModeEnabledAngle);
     }
 
     private int getVulkanVersion(PackageManager pm) {
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index c62df40..72fb4ae 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -654,7 +654,7 @@
         arg1 = source.readInt();
         arg2 = source.readInt();
         if (source.readInt() != 0) {
-            obj = source.readParcelable(getClass().getClassLoader());
+            obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class);
         }
         when = source.readLong();
         data = source.readBundle();
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 3664515..3bc3ec8 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -846,6 +846,19 @@
     }
 
     /**
+     * Verify there are no bytes left to be read on the Parcel.
+     *
+     * @throws BadParcelableException If the current position hasn't reached the end of the Parcel.
+     * When used over binder, this exception should propagate to the caller.
+     */
+    public void enforceNoDataAvail() {
+        final int n = dataAvail();
+        if (n > 0) {
+            throw new BadParcelableException("Parcel data not fully consumed, unread size: " + n);
+        }
+    }
+
+    /**
      * Writes the work source uid to the request headers.
      *
      * <p>It requires the headers to have been written/read already to replace the work source.
@@ -902,11 +915,15 @@
     /**
      * Write a blob of data into the parcel at the current {@link #dataPosition},
      * growing {@link #dataCapacity} if needed.
+     *
+     * <p> If the blob is small, then it is stored in-place, otherwise it is transferred by way of
+     * an anonymous shared memory region. If you prefer send in-place, please use
+     * {@link #writeByteArray(byte[])}.
+     *
      * @param b Bytes to place into the parcel.
-     * {@hide}
-     * {@SystemApi}
+     *
+     * @see #readBlob()
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public final void writeBlob(@Nullable byte[] b) {
         writeBlob(b, 0, (b != null) ? b.length : 0);
     }
@@ -914,11 +931,16 @@
     /**
      * Write a blob of data into the parcel at the current {@link #dataPosition},
      * growing {@link #dataCapacity} if needed.
+     *
+     * <p> If the blob is small, then it is stored in-place, otherwise it is transferred by way of
+     * an anonymous shared memory region. If you prefer send in-place, please use
+     * {@link #writeByteArray(byte[], int, int)}.
+     *
      * @param b Bytes to place into the parcel.
      * @param offset Index of first byte to be written.
      * @param len Number of bytes to write.
-     * {@hide}
-     * {@SystemApi}
+     *
+     * @see #readBlob()
      */
     public final void writeBlob(@Nullable byte[] b, int offset, int len) {
         if (b == null) {
@@ -3197,10 +3219,8 @@
 
     /**
      * Read a blob of data from the parcel and return it as a byte array.
-     * {@hide}
-     * {@SystemApi}
+     * @see #writeBlob(byte[], int, int)
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Nullable
     public final byte[] readBlob() {
         return nativeReadBlob(mNativePtr);
@@ -4197,8 +4217,7 @@
      * trying to instantiate an element.
      */
     @Nullable
-    public <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader,
-            @NonNull Class<T> clazz) {
+    public <T> T readParcelable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
         Objects.requireNonNull(clazz);
         return readParcelableInternal(loader, clazz);
     }
@@ -4209,10 +4228,6 @@
     @SuppressWarnings("unchecked")
     @Nullable
     private <T> T readParcelableInternal(@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
-        if (clazz != null && !Parcelable.class.isAssignableFrom(clazz)) {
-            throw new BadParcelableException("About to unparcel a parcelable object "
-                    + " but class required " + clazz.getName() + " is not Parcelable");
-        }
         Parcelable.Creator<?> creator = readParcelableCreatorInternal(loader, clazz);
         if (creator == null) {
             return null;
@@ -4375,9 +4390,16 @@
      * The given class loader will be used to load any enclosed
      * Parcelables.
      * @return the Parcelable array, or null if the array is null
+     *
+     * @deprecated Use the type-safer version {@link #readParcelableArray(ClassLoader, Class)}
+     *      starting from Android {@link Build.VERSION_CODES#TIRAMISU}. Also consider changing the
+     *      format to use {@link #createTypedArray(Parcelable.Creator)} if possible (eg. if the
+     *      items' class is final) since this is also more performant. Note that changing to the
+     *      latter also requires changing the writes.
      */
+    @Deprecated
     @Nullable
-    public final Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
+    public Parcelable[] readParcelableArray(@Nullable ClassLoader loader) {
         int N = readInt();
         if (N < 0) {
             return null;
@@ -4417,6 +4439,9 @@
      * @return the Serializable object, or null if the Serializable name
      * wasn't found in the parcel.
      *
+     * Unlike {@link #readSerializable(ClassLoader, Class)}, it uses the nearest valid class loader
+     * up the execution stack to instantiate the Serializable object.
+     *
      * @deprecated Use the type-safer version {@link #readSerializable(ClassLoader, Class)} starting
      *       from Android {@link Build.VERSION_CODES#TIRAMISU}.
      */
@@ -4427,19 +4452,21 @@
     }
 
     /**
-     * Same as {@link #readSerializable()} but accepts {@code loader} parameter
-     * as the primary classLoader for resolving the Serializable class; and {@code clazz} parameter
-     * as the required type.
+     * Same as {@link #readSerializable()} but accepts {@code loader} and {@code clazz} parameters.
+     *
+     * @param loader A ClassLoader from which to instantiate the Serializable object,
+     * or null for the default class loader.
+     * @param clazz The type of the object expected.
      *
      * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
      * is not an instance of that class or any of its children class or there there was an error
      * deserializing the object.
      */
     @Nullable
-    public <T extends Serializable> T readSerializable(@Nullable ClassLoader loader,
-            @NonNull Class<T> clazz) {
+    public <T> T readSerializable(@Nullable ClassLoader loader, @NonNull Class<T> clazz) {
         Objects.requireNonNull(clazz);
-        return readSerializableInternal(loader, clazz);
+        return readSerializableInternal(
+                loader == null ? getClass().getClassLoader() : loader, clazz);
     }
 
     /**
@@ -4448,11 +4475,6 @@
     @Nullable
     private <T> T readSerializableInternal(@Nullable final ClassLoader loader,
             @Nullable Class<T> clazz) {
-        if (clazz != null && !Serializable.class.isAssignableFrom(clazz)) {
-            throw new BadParcelableException("About to unparcel a serializable object "
-                    + " but class required " + clazz.getName() + " is not Serializable");
-        }
-
         String name = readString();
         if (name == null) {
             // For some reason we were unable to read the name of the Serializable (either there
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index e863111..590494c 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -18,6 +18,7 @@
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
 import static android.os.BatteryConsumer.POWER_COMPONENT_COUNT;
 import static android.os.BatteryConsumer.PROCESS_STATE_ANY;
+import static android.os.BatteryConsumer.PROCESS_STATE_UNSPECIFIED;
 import static android.os.BatteryConsumer.convertMahToDeciCoulombs;
 
 import android.annotation.NonNull;
@@ -339,7 +340,7 @@
 
                 serializer.startTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
                 serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_ID, componentId);
-                if (key.processState != PROCESS_STATE_ANY) {
+                if (key.processState != PROCESS_STATE_UNSPECIFIED) {
                     serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_PROCESS_STATE,
                             key.processState);
                 }
@@ -398,7 +399,7 @@
                 switch (parser.getName()) {
                     case BatteryUsageStats.XML_TAG_COMPONENT: {
                         int componentId = -1;
-                        int processState = PROCESS_STATE_ANY;
+                        int processState = PROCESS_STATE_UNSPECIFIED;
                         double powerMah = 0;
                         long durationMs = 0;
                         int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 7455f1f..dbb5a2c 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2984,7 +2984,7 @@
          *     should be removed.
          */
         public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
-            mViolation = (Violation) in.readSerializable();
+            mViolation = (Violation) in.readSerializable(android.os.strictmode.Violation.class.getClassLoader(), android.os.strictmode.Violation.class);
             int binderStackSize = in.readInt();
             for (int i = 0; i < binderStackSize; i++) {
                 StackTraceElement[] traceElements = new StackTraceElement[in.readInt()];
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index ddb6533..d974e0c 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -131,6 +131,10 @@
     private static native void nativeAsyncTraceBegin(long tag, String name, int cookie);
     @FastNative
     private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
+    @FastNative
+    private static native void nativeInstant(long tag, String name);
+    @FastNative
+    private static native void nativeInstantForTrack(long tag, String trackName, String name);
 
     private Trace() {
     }
@@ -258,6 +262,42 @@
     }
 
     /**
+     * Writes a trace message to indicate that a given section of code was invoked.
+     *
+     * @param traceTag The trace tag.
+     * @param methodName The method name to appear in the trace.
+     * @hide
+     */
+    public static void instant(long traceTag, String methodName) {
+        if (methodName == null) {
+            throw new IllegalArgumentException("methodName cannot be null");
+        }
+        if (isTagEnabled(traceTag)) {
+            nativeInstant(traceTag, methodName);
+        }
+    }
+
+    /**
+     * Writes a trace message to indicate that a given section of code was invoked.
+     *
+     * @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.
+     * @hide
+     */
+    public static void instantForTrack(long traceTag, String trackName, String methodName) {
+        if (trackName == null) {
+            throw new IllegalArgumentException("trackName cannot be null");
+        }
+        if (methodName == null) {
+            throw new IllegalArgumentException("methodName cannot be null");
+        }
+        if (isTagEnabled(traceTag)) {
+            nativeInstantForTrack(traceTag, trackName, methodName);
+        }
+    }
+
+    /**
      * Checks whether or not tracing is currently enabled. This is useful to avoid intermediate
      * string creation for trace sections that require formatting. It is not necessary
      * to guard all Trace method calls as they internally already check this. However it is
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bc6dbd8..b3639e4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -327,6 +327,43 @@
             "no_sharing_admin_configured_wifi";
 
     /**
+     * Specifies if a user is disallowed from using Wi-Fi Direct.
+     *
+     * <p>This restriction can only be set by a device owner,
+     * a profile owner of an organization-owned managed profile on the parent profile.
+     * When it is set by any of these owners, it prevents all users from using
+     * Wi-Fi Direct.
+     *
+     * <p>The default value 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_WIFI_DIRECT = "no_wifi_direct";
+
+    /**
+     * Specifies if a user is disallowed from adding a new Wi-Fi configuration.
+     *
+     * <p>This restriction can only be set by a device owner,
+     * a profile owner of an organization-owned managed profile on the parent profile.
+     * When it is set by any of these owners, it prevents all users from adding
+     * a new Wi-Fi configuration. This does not limit the owner and carrier's ability
+     * to add a new configuration.
+     *
+     * <p>The default value 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_ADD_WIFI_CONFIG = "no_add_wifi_config";
+
+    /**
      * Specifies if a user is disallowed from changing the device
      * language. The default value is <code>false</code>.
      *
@@ -1500,6 +1537,8 @@
             DISALLOW_CHANGE_WIFI_STATE,
             DISALLOW_WIFI_TETHERING,
             DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+            DISALLOW_WIFI_DIRECT,
+            DISALLOW_ADD_WIFI_CONFIG,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
@@ -1507,6 +1546,17 @@
     private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
 
     /**
+     * Action to start an activity to create a supervised user.
+     * Only devices with non-empty config_supervisedUserCreationPackage support this.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_USERS)
+    public static final String ACTION_CREATE_SUPERVISED_USER =
+            "android.os.action.CREATE_SUPERVISED_USER";
+
+    /**
      * Extra containing a name for the user being created. Optional parameter passed to
      * ACTION_CREATE_USER activity.
      * @hide
@@ -2034,7 +2084,8 @@
     @TestApi
     @RequiresPermission(anyOf = {
             android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.CREATE_USERS})
+            android.Manifest.permission.CREATE_USERS,
+            android.Manifest.permission.QUERY_USERS})
     @UserHandleAware
     public @NonNull String getUserType() {
         UserInfo userInfo = getUserInfo(mUserId);
@@ -2043,15 +2094,21 @@
 
     /**
      * Returns the user name of the context user. This call is only available to applications on
-     * the system image; it requires the {@code android.permission.MANAGE_USERS} or {@code
-     * android.permission.GET_ACCOUNTS_PRIVILEGED} permissions.
+     * the system image.
      *
      * @return the user name
      */
-    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED,
-            android.Manifest.permission.CREATE_USERS}, conditional = true)
-    @UserHandleAware
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.CREATE_USERS,
+            android.Manifest.permission.QUERY_USERS,
+            android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED})
+
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.CREATE_USERS,
+                    android.Manifest.permission.QUERY_USERS})
     public @NonNull String getUserName() {
         if (UserHandle.myUserId() == mUserId) {
             try {
@@ -2114,8 +2171,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public boolean isPrimaryUser() {
         final UserInfo user = getUserInfo(getContextUserIfAppropriate());
@@ -2145,7 +2204,8 @@
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public boolean isAdminUser() {
         return isUserAdmin(getContextUserIfAppropriate());
@@ -2157,8 +2217,10 @@
      * user.
      */
     @UnsupportedAppUsage
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     public boolean isUserAdmin(@UserIdInt int userId) {
         UserInfo user = getUserInfo(userId);
         return user != null && user.isAdmin();
@@ -2355,8 +2417,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     @UserHandleAware
     public @Nullable UserHandle getRestrictedProfileParent() {
         final UserInfo info = getUserInfo(mUserId);
@@ -2373,8 +2437,10 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     public boolean isGuestUser(@UserIdInt int userId) {
         UserInfo user = getUserInfo(userId);
         return user != null && user.isGuest();
@@ -2387,8 +2453,10 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public boolean isGuestUser() {
         UserInfo user = getUserInfo(getContextUserIfAppropriate());
@@ -2417,17 +2485,15 @@
     /**
      * Checks if the calling context user is running in a profile.
      *
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} or
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the
-     * caller must be in the same profile group of specified user.
-     *
      * @return whether the caller is in a profile.
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
-    @UserHandleAware
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCallerProfileGroup = {
+                    android.Manifest.permission.MANAGE_USERS,
+                    android.Manifest.permission.QUERY_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
     public boolean isProfile() {
         return isProfile(mUserId);
     }
@@ -2463,8 +2529,8 @@
             enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
             requiresAnyOfPermissionsIfNotCallerProfileGroup = {
                     android.Manifest.permission.MANAGE_USERS,
-                    android.Manifest.permission.INTERACT_ACROSS_USERS}
-    )
+                    android.Manifest.permission.QUERY_USERS,
+                    android.Manifest.permission.INTERACT_ACROSS_USERS})
     public boolean isManagedProfile() {
         return isManagedProfile(getContextUserIfAppropriate());
     }
@@ -2472,15 +2538,18 @@
     /**
      * Checks if the specified user is a managed profile.
      * Requires {@link android.Manifest.permission#MANAGE_USERS} or
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS} permission, otherwise the caller
      * must be in the same profile group of specified user.
      *
      * @return whether the specified user is a managed profile.
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
-            Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
     public boolean isManagedProfile(@UserIdInt int userId) {
         if (userId == mUserId) {
             // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
@@ -2534,8 +2603,10 @@
      * @return whether the context user is an ephemeral user.
      * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     @UserHandleAware
     public boolean isEphemeralUser() {
         return isUserEphemeral(mUserId);
@@ -2545,8 +2616,10 @@
      * Returns whether the specified user is ephemeral.
      * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     public boolean isUserEphemeral(@UserIdInt int userId) {
         final UserInfo user = getUserInfo(userId);
         return user != null && user.isEphemeral();
@@ -2814,8 +2887,10 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS})
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS})
     public UserInfo getUserInfo(@UserIdInt int userId) {
         try {
             return mService.getUserInfo(userId);
@@ -2838,7 +2913,9 @@
     @Deprecated
     @SystemApi
     @UserRestrictionSource
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.QUERY_USERS})
     public int getUserRestrictionSource(@UserRestrictionKey String restrictionKey,
             UserHandle userHandle) {
         try {
@@ -2857,7 +2934,9 @@
      * @return a list of user ids enforcing this restriction.
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS})
     public List<EnforcingUser> getUserRestrictionSources(
             @UserRestrictionKey String restrictionKey, UserHandle userHandle) {
         try {
@@ -3924,15 +4003,17 @@
     }
 
     /**
-     * Checks whether it's possible to add more managed profiles. Caller must hold the MANAGE_USERS
-     * permission.
+     * Checks whether it's possible to add more managed profiles.
      * if allowedToRemoveOne is true and if the user already has a managed profile, then return if
      * we could add a new managed profile to this user after removing the existing one.
      *
      * @return true if more managed profiles can be added, false if limit has been reached.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS
+    })
     public boolean canAddMoreManagedProfiles(@UserIdInt int userId, boolean allowedToRemoveOne) {
         try {
             return mService.canAddMoreManagedProfiles(userId, allowedToRemoveOne);
@@ -3948,7 +4029,10 @@
      * @return true if more profiles can be added, false if limit has been reached.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS
+    })
     public boolean canAddMoreProfilesToUser(@NonNull String userType, @UserIdInt int userId) {
         try {
             return mService.canAddMoreProfilesToUser(userType, userId, false);
@@ -3983,14 +4067,17 @@
      * <p>Note that this includes all profile types (not including Restricted profiles).
      *
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
-     * {@link android.Manifest.permission#CREATE_USERS} if userId is not the calling user.
+     * {@link android.Manifest.permission#CREATE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS} if userId is not the calling user.
      * @param userId profiles of this user will be returned.
      * @return the list of profiles.
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS}, conditional = true)
     public List<UserInfo> getProfiles(@UserIdInt int userId) {
         try {
             return mService.getProfiles(userId, false /* enabledOnly */);
@@ -4009,7 +4096,9 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS})
     public boolean isSameProfileGroup(@NonNull UserHandle user, @NonNull UserHandle otherUser) {
         return isSameProfileGroup(user.getIdentifier(), otherUser.getIdentifier());
     }
@@ -4021,7 +4110,9 @@
      * @return true if the two user ids are in the same profile group.
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.QUERY_USERS})
     public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
         try {
             return mService.isSameProfileGroup(userId, otherUserId);
@@ -4036,14 +4127,20 @@
      * <p>Note that this includes all profile types (not including Restricted profiles).
      *
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
-     * {@link android.Manifest.permission#CREATE_USERS} if userId is not the calling user.
+     * {@link android.Manifest.permission#CREATE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS} if userId is not the calling user.
      * @param userId profiles of this user will be returned.
      * @return the list of profiles.
+     * @deprecated use {@link #getUserProfiles()} instead.
+     *
      * @hide
      */
+    @Deprecated
     @UnsupportedAppUsage
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS}, conditional = true)
     public List<UserInfo> getEnabledProfiles(@UserIdInt int userId) {
         try {
             return mService.getProfiles(userId, true /* enabledOnly */);
@@ -4063,8 +4160,8 @@
             enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU,
             requiresAnyOfPermissionsIfNotCaller = {
                     Manifest.permission.MANAGE_USERS,
-                    Manifest.permission.CREATE_USERS}
-    )
+                    Manifest.permission.CREATE_USERS,
+                    Manifest.permission.QUERY_USERS})
     public List<UserHandle> getUserProfiles() {
         int[] userIds = getProfileIds(getContextUserIfAppropriate(), true /* enabledOnly */);
         return convertUserIdsToUserHandles(userIds);
@@ -4079,9 +4176,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
-    @UserHandleAware
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    Manifest.permission.MANAGE_USERS,
+                    Manifest.permission.CREATE_USERS,
+                    Manifest.permission.QUERY_USERS})
     public @NonNull List<UserHandle> getEnabledProfiles() {
         return getProfiles(true);
     }
@@ -4095,9 +4194,11 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
-    @UserHandleAware
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    Manifest.permission.MANAGE_USERS,
+                    Manifest.permission.CREATE_USERS,
+                    Manifest.permission.QUERY_USERS})
     public @NonNull List<UserHandle> getAllProfiles() {
         return getProfiles(false);
     }
@@ -4110,9 +4211,11 @@
      * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
      * @return A non-empty list of UserHandles associated with the context user.
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
-    @UserHandleAware
+    @UserHandleAware(
+            requiresAnyOfPermissionsIfNotCaller = {
+                    Manifest.permission.MANAGE_USERS,
+                    Manifest.permission.CREATE_USERS,
+                    Manifest.permission.QUERY_USERS})
     private @NonNull List<UserHandle> getProfiles(boolean enabledOnly) {
         final int[] userIds = getProfileIds(mUserId, enabledOnly);
         return convertUserIdsToUserHandles(userIds);
@@ -4138,8 +4241,10 @@
      *
      * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS}, conditional = true)
     public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
         try {
             return mService.getProfileIds(userId, enabledOnly);
@@ -4153,8 +4258,10 @@
      * @hide
      */
     @UnsupportedAppUsage
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS}, conditional = true)
     public int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
         return getProfileIds(userId, false /* enabledOnly */);
     }
@@ -4163,8 +4270,10 @@
      * @see #getProfileIds(int, boolean)
      * @hide
      */
-    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
-            Manifest.permission.CREATE_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {
+            Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS,
+            Manifest.permission.QUERY_USERS}, conditional = true)
     public int[] getEnabledProfileIds(@UserIdInt int userId) {
         return getProfileIds(userId, true /* enabledOnly */);
     }
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 5758a4e..41ab062 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -576,7 +576,7 @@
         private final int mRepeatIndex;
 
         Composed(@NonNull Parcel in) {
-            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader()), in.readInt());
+            this(in.readArrayList(VibrationEffectSegment.class.getClassLoader(), android.os.vibrator.VibrationEffectSegment.class), in.readInt());
         }
 
         Composed(@NonNull VibrationEffectSegment segment) {
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index cf51496..0a0e3c8 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -70,7 +70,7 @@
         mPwlePrimitiveDurationMax = in.readInt();
         mPwleSizeMax = in.readInt();
         mQFactor = in.readFloat();
-        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader());
+        mFrequencyMapping = in.readParcelable(VibratorInfo.class.getClassLoader(), android.os.VibratorInfo.FrequencyMapping.class);
     }
 
     /**
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 6588b57..e899f77 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -130,7 +130,7 @@
         int numChains = in.readInt();
         if (numChains > 0) {
             mChains = new ArrayList<>(numChains);
-            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+            in.readParcelableList(mChains, WorkChain.class.getClassLoader(), android.os.WorkSource.WorkChain.class);
         } else {
             mChains = null;
         }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 627e09e..5e4057b 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -274,6 +274,15 @@
     public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
 
     /** {@hide} */
+    @IntDef(prefix = "FLAG_STORAGE_",  value = {
+            FLAG_STORAGE_DE,
+            FLAG_STORAGE_CE,
+            FLAG_STORAGE_EXTERNAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StorageFlags {}
+
+    /** {@hide} */
     public static final int FLAG_FOR_WRITE = 1 << 8;
     /** {@hide} */
     public static final int FLAG_REAL_STATE = 1 << 9;
@@ -1428,11 +1437,36 @@
         throw new IllegalStateException("Missing primary storage");
     }
 
-    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
-    private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
+    /**
+     * Devices having above STORAGE_THRESHOLD_PERCENT_HIGH of total space free are considered to be
+     * in high free space category.
+     *
+     * @hide
+     */
+    public static final int STORAGE_THRESHOLD_PERCENT_HIGH = 20;
+    /**
+     * Devices having below STORAGE_THRESHOLD_PERCENT_LOW of total space free are considered to be
+     * in low free space category.
+     *
+     * @hide
+     */
+    public static final int STORAGE_THRESHOLD_PERCENT_LOW = 5;
+    /**
+     * For devices in high free space category, CACHE_RESERVE_PERCENT_HIGH percent of total space is
+     * allocated for cache.
+     *
+     * @hide
+     */
+    public static final int CACHE_RESERVE_PERCENT_HIGH = 10;
+    /**
+     * For devices in low free space category, CACHE_RESERVE_PERCENT_LOW percent of total space is
+     * allocated for cache.
+     *
+     * @hide
+     */
+    public static final int CACHE_RESERVE_PERCENT_LOW = 2;
 
-    private static final int DEFAULT_CACHE_PERCENTAGE = 10;
-    private static final long DEFAULT_CACHE_MAX_BYTES = DataUnit.GIBIBYTES.toBytes(5);
+    private static final long DEFAULT_THRESHOLD_MAX_BYTES = DataUnit.MEBIBYTES.toBytes(500);
 
     private static final long DEFAULT_FULL_THRESHOLD_BYTES = DataUnit.MEBIBYTES.toBytes(1);
 
@@ -1456,7 +1490,7 @@
     @UnsupportedAppUsage
     public long getStorageLowBytes(File path) {
         final long lowPercent = Settings.Global.getInt(mResolver,
-                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
+                Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, STORAGE_THRESHOLD_PERCENT_LOW);
         final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
 
         final long maxLowBytes = Settings.Global.getLong(mResolver,
@@ -1466,28 +1500,54 @@
     }
 
     /**
+     * Compute the minimum number of bytes of storage on the device that could
+     * be reserved for cached data depending on the device state which is then passed on
+     * to getStorageCacheBytes.
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @TestApi
+    @SuppressLint("StreamFiles")
+    public long computeStorageCacheBytes(@NonNull File path) {
+        final long totalBytes = path.getTotalSpace();
+        final long usableBytes = path.getUsableSpace();
+        final long storageThresholdHighBytes = totalBytes * STORAGE_THRESHOLD_PERCENT_HIGH / 100;
+        final long storageThresholdLowBytes = getStorageLowBytes(path);
+        long result;
+        if (usableBytes > storageThresholdHighBytes) {
+            // If free space is >STORAGE_THRESHOLD_PERCENT_HIGH of total space,
+            // reserve CACHE_RESERVE_PERCENT_HIGH of total space
+            result = totalBytes * CACHE_RESERVE_PERCENT_HIGH / 100;
+        } else if (usableBytes < storageThresholdLowBytes) {
+            // If free space is <min(STORAGE_THRESHOLD_PERCENT_LOW of total space, 500MB),
+            // reserve CACHE_RESERVE_PERCENT_LOW of total space
+            result = totalBytes * CACHE_RESERVE_PERCENT_LOW / 100;
+        } else {
+            // Else, linearly interpolate the amount of space to reserve
+            result = ((CACHE_RESERVE_PERCENT_HIGH - CACHE_RESERVE_PERCENT_LOW)
+                      * (usableBytes - storageThresholdHighBytes) + CACHE_RESERVE_PERCENT_HIGH
+                      * (storageThresholdHighBytes - storageThresholdLowBytes)) * totalBytes
+                      / (100 * (storageThresholdHighBytes - storageThresholdLowBytes));
+        }
+        return result;
+    }
+
+    /**
      * Return the minimum number of bytes of storage on the device that should
      * be reserved for cached data.
      *
      * @hide
      */
-    public long getStorageCacheBytes(File path, @AllocateFlags int flags) {
-        final long cachePercent = Settings.Global.getInt(mResolver,
-                Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE);
-        final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100;
-
-        final long maxCacheBytes = Settings.Global.getLong(mResolver,
-                Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES);
-
-        final long result = Math.min(cacheBytes, maxCacheBytes);
+    public long getStorageCacheBytes(@NonNull File path, @AllocateFlags int flags) {
         if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
             return 0;
         } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_ALL_RESERVED) != 0) {
             return 0;
         } else if ((flags & StorageManager.FLAG_ALLOCATE_DEFY_HALF_RESERVED) != 0) {
-            return result / 2;
+            return computeStorageCacheBytes(path) / 2;
         } else {
-            return result;
+            return computeStorageCacheBytes(path);
         }
     }
 
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 0c2f8b6..19e04e6 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -168,7 +168,7 @@
         mStub = in.readInt() != 0;
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
-        mOwner = in.readParcelable(null);
+        mOwner = in.readParcelable(null, android.os.UserHandle.class);
         if (in.readInt() != 0) {
             mUuid = StorageManager.convert(in.readString8());
         } else {
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 20f6c10..f0e6624 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -52,6 +52,8 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -150,7 +152,9 @@
     private ArrayMap<UserHandle, Context> mUserContexts;
     private PackageManager mPkgManager;
     private AppOpsManager mAppOpsManager;
-    private ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains = new ArrayMap<>();
+    @GuardedBy("mAttributionChains")
+    private final ArrayMap<Integer, ArrayList<AccessChainLink>> mAttributionChains =
+            new ArrayMap<>();
 
     /**
      * Constructor for PermissionUsageHelper
@@ -199,22 +203,24 @@
         // if any link in the chain is finished, remove the chain. Then, find any other chains that
         // contain this op/package/uid/tag combination, and remove them, as well.
         // TODO ntmyren: be smarter about this
-        mAttributionChains.remove(attributionChainId);
-        int numChains = mAttributionChains.size();
-        ArrayList<Integer> toRemove = new ArrayList<>();
-        for (int i = 0; i < numChains; i++) {
-            int chainId = mAttributionChains.keyAt(i);
-            ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
-            int chainSize = chain.size();
-            for (int j = 0; j < chainSize; j++) {
-                AccessChainLink link = chain.get(j);
-                if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
-                    toRemove.add(chainId);
-                    break;
+        synchronized (mAttributionChains) {
+            mAttributionChains.remove(attributionChainId);
+            int numChains = mAttributionChains.size();
+            ArrayList<Integer> toRemove = new ArrayList<>();
+            for (int i = 0; i < numChains; i++) {
+                int chainId = mAttributionChains.keyAt(i);
+                ArrayList<AccessChainLink> chain = mAttributionChains.valueAt(i);
+                int chainSize = chain.size();
+                for (int j = 0; j < chainSize; j++) {
+                    AccessChainLink link = chain.get(j);
+                    if (link.packageAndOpEquals(op, packageName, attributionTag, uid)) {
+                        toRemove.add(chainId);
+                        break;
+                    }
                 }
             }
+            mAttributionChains.removeAll(toRemove);
         }
-        mAttributionChains.removeAll(toRemove);
     }
 
     @Override
@@ -234,11 +240,13 @@
             // If this is not a successful start, or it is not a chain, or it is untrusted, return
             return;
         }
-        addLinkToChainIfNotPresent(AppOpsManager.opToPublicName(op), packageName, uid,
-                attributionTag, attributionFlags, attributionChainId);
+        synchronized (mAttributionChains) {
+            addLinkToChainIfNotPresentLocked(AppOpsManager.opToPublicName(op), packageName, uid,
+                    attributionTag, attributionFlags, attributionChainId);
+        }
     }
 
-    private void addLinkToChainIfNotPresent(String op, String packageName, int uid,
+    private void addLinkToChainIfNotPresentLocked(String op, String packageName, int uid,
             String attributionTag, int attributionFlags, int attributionChainId) {
 
         ArrayList<AccessChainLink> currentChain = mAttributionChains.computeIfAbsent(
@@ -544,42 +552,44 @@
             }
         }
 
-        for (int i = 0; i < mAttributionChains.size(); i++) {
-            List<AccessChainLink> usageList = mAttributionChains.valueAt(i);
-            int lastVisible = usageList.size() - 1;
-            // TODO ntmyren: remove this mic code once camera is converted to AttributionSource
-            // if the list is empty or incomplete, do not show it.
-            if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
-                    || !usageList.get(0).isStart()
-                    || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
-                continue;
-            }
-
-            //TODO ntmyren: remove once camera etc. etc.
-            for (AccessChainLink link: usageList) {
-                proxyPackages.add(link.usage.getPackageIdHash());
-            }
-
-            AccessChainLink start = usageList.get(0);
-            AccessChainLink lastVisibleLink = usageList.get(lastVisible);
-            while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) {
-                lastVisible--;
-                lastVisibleLink = usageList.get(lastVisible);
-            }
-            String proxyLabel = null;
-            if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) {
-                try {
-                    PackageManager userPkgManager =
-                            getUserContext(lastVisibleLink.usage.getUser()).getPackageManager();
-                    ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
-                            lastVisibleLink.usage.packageName, 0);
-                    proxyLabel = appInfo.loadLabel(userPkgManager).toString();
-                } catch (PackageManager.NameNotFoundException e) {
-                    // do nothing
+        synchronized (mAttributionChains) {
+            for (int i = 0; i < mAttributionChains.size(); i++) {
+                List<AccessChainLink> usageList = mAttributionChains.valueAt(i);
+                int lastVisible = usageList.size() - 1;
+                // TODO ntmyren: remove this mic code once camera is converted to AttributionSource
+                // if the list is empty or incomplete, do not show it.
+                if (usageList.isEmpty() || !usageList.get(lastVisible).isEnd()
+                        || !usageList.get(0).isStart()
+                        || !usageList.get(lastVisible).usage.op.equals(OPSTR_RECORD_AUDIO)) {
+                    continue;
                 }
 
+                //TODO ntmyren: remove once camera etc. etc.
+                for (AccessChainLink link : usageList) {
+                    proxyPackages.add(link.usage.getPackageIdHash());
+                }
+
+                AccessChainLink start = usageList.get(0);
+                AccessChainLink lastVisibleLink = usageList.get(lastVisible);
+                while (lastVisible > 0 && !shouldShowPackage(lastVisibleLink.usage.packageName)) {
+                    lastVisible--;
+                    lastVisibleLink = usageList.get(lastVisible);
+                }
+                String proxyLabel = null;
+                if (!lastVisibleLink.usage.packageName.equals(start.usage.packageName)) {
+                    try {
+                        PackageManager userPkgManager =
+                                getUserContext(lastVisibleLink.usage.getUser()).getPackageManager();
+                        ApplicationInfo appInfo = userPkgManager.getApplicationInfo(
+                                lastVisibleLink.usage.packageName, 0);
+                        proxyLabel = appInfo.loadLabel(userPkgManager).toString();
+                    } catch (PackageManager.NameNotFoundException e) {
+                        // do nothing
+                    }
+
+                }
+                usagesAndLabels.put(start.usage, proxyLabel);
             }
-            usagesAndLabels.put(start.usage, proxyLabel);
         }
 
         for (int packageHash : mostRecentUsages.keySet()) {
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 63f38f8..9bdfd8e 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -231,9 +231,9 @@
     }
 
     private PrintJobInfo(@NonNull Parcel parcel) {
-        mId = parcel.readParcelable(null);
+        mId = parcel.readParcelable(null, android.print.PrintJobId.class);
         mLabel = parcel.readString();
-        mPrinterId = parcel.readParcelable(null);
+        mPrinterId = parcel.readParcelable(null, android.print.PrinterId.class);
         mPrinterName = parcel.readString();
         mState = parcel.readInt();
         mAppId = parcel.readInt();
@@ -247,8 +247,8 @@
                 mPageRanges[i] = (PageRange) parcelables[i];
             }
         }
-        mAttributes = (PrintAttributes) parcel.readParcelable(null);
-        mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
+        mAttributes = (PrintAttributes) parcel.readParcelable(null, android.print.PrintAttributes.class);
+        mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null, android.print.PrintDocumentInfo.class);
         mProgress = parcel.readFloat();
         mStatus = parcel.readCharSequence();
         mStatusRes = parcel.readInt();
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 25260c4..284e122 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -48,7 +48,7 @@
     }
 
     private PrinterId(@NonNull Parcel parcel) {
-        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null));
+        mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null, android.content.ComponentName.class));
         mLocalId = Preconditions.checkNotNull(parcel.readString());
     }
 
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 8e03e3e..2f93e40 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -270,15 +270,15 @@
     private PrinterInfo(Parcel parcel) {
         // mName can be null due to unchecked set in Builder.setName and status can be invalid
         // due to unchecked set in Builder.setStatus, hence we can only check mId for a valid state
-        mId = checkPrinterId((PrinterId) parcel.readParcelable(null));
+        mId = checkPrinterId((PrinterId) parcel.readParcelable(null, android.print.PrinterId.class));
         mName = checkName(parcel.readString());
         mStatus = checkStatus(parcel.readInt());
         mDescription = parcel.readString();
-        mCapabilities = parcel.readParcelable(null);
+        mCapabilities = parcel.readParcelable(null, android.print.PrinterCapabilitiesInfo.class);
         mIconResourceId = parcel.readInt();
         mHasCustomPrinterIcon = parcel.readByte() != 0;
         mCustomPrinterIconGen = parcel.readInt();
-        mInfoIntent = parcel.readParcelable(null);
+        mInfoIntent = parcel.readParcelable(null, android.app.PendingIntent.class);
     }
 
     @Override
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index 0c1b61d..3479557 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -76,7 +76,7 @@
     public PrintServiceInfo(Parcel parcel) {
         mId = parcel.readString();
         mIsEnabled = parcel.readByte() != 0;
-        mResolveInfo = parcel.readParcelable(null);
+        mResolveInfo = parcel.readParcelable(null, android.content.pm.ResolveInfo.class);
         mSettingsActivityName = parcel.readString();
         mAddPrintersActivityName = parcel.readString();
         mAdvancedPrintOptionsActivityName = parcel.readString();
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 5036abc..b9f5144 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2956,7 +2956,10 @@
          */
         @Nullable
         public static String getLocalAccountName(@NonNull Context context) {
-            return null;
+            //  config_rawContactsLocalAccountName is defined in
+            //  platform/frameworks/base/core/res/res/values/config.xml
+            return TextUtils.nullIfEmpty(context.getString(
+                    com.android.internal.R.string.config_rawContactsLocalAccountName));
         }
 
         /**
@@ -2972,7 +2975,10 @@
          */
         @Nullable
         public static String getLocalAccountType(@NonNull Context context) {
-            return null;
+            //  config_rawContactsLocalAccountType is defined in
+            //  platform/frameworks/base/core/res/res/values/config.xml
+            return TextUtils.nullIfEmpty(context.getString(
+                    com.android.internal.R.string.config_rawContactsLocalAccountType));
         }
 
         /**
@@ -8790,6 +8796,8 @@
         /**
          * Get the account that is set as the default account for new contacts, which should be
          * initially selected when creating a new contact on contact management apps.
+         * If the setting has not been set by any app, it will return null. Once the setting
+         * is set to non-null Account, it can still be set to null in the future.
          *
          * @param resolver the ContentResolver to query.
          * @return the default account for new contacts, or null if it's not set or set to NULL
@@ -8804,7 +8812,13 @@
 
         /**
          * Sets the account as the default account that should be initially selected
-         * when creating a new contact on contact management apps.
+         * when creating a new contact on contact management apps. Apps can only set one of
+         * the following accounts as the default account:
+         * <ol>
+         *   <li>null or custom local account
+         *   <li>SIM account
+         *   <li>AccountManager accounts
+         * </ol>
          *
          * @param resolver the ContentResolver to query.
          * @param account the account to be set to default.
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 21c1feb..22b9578 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -671,6 +671,14 @@
     @TestApi
     public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
 
+    /**
+     * Namespace for all ultra wideband (uwb) related features.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String NAMESPACE_UWB = "uwb";
+
     private static final Object sLock = new Object();
     @GuardedBy("sLock")
     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e267db4..7228b5a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -965,6 +965,22 @@
     public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow pairing bluetooth devices.
+     * <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_BLUETOOTH_PAIRING_SETTINGS =
+            "android.settings.BLUETOOTH_PAIRING_SETTINGS";
+
+    /**
      * Activity Action: Show settings to configure input methods, in particular
      * allowing the user to enable input methods.
      * <p>
@@ -9311,6 +9327,16 @@
                 "emergency_gesture_sound_enabled";
 
         /**
+         * The power button "cooldown" period in milliseconds after the Emergency gesture is
+         * triggered, during which single-key actions on the power button are suppressed. Cooldown
+         * period is disabled if set to zero.
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS =
+                "emergency_gesture_power_button_cooldown_period_ms";
+
+        /**
          * Whether the camera launch gesture to double tap the power button when the screen is off
          * should be disabled.
          *
@@ -11427,22 +11453,38 @@
                 "night_display_forced_auto_mode_available";
 
         /**
-        * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
-        * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
-        * exceeded.
-        * @hide
-        */
+         * If UTC time between two NITZ signals is greater than this value then the second signal
+         * cannot be ignored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
         @Readable
         public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
 
         /**
-        * The length of time in milli-seconds that automatic small adjustments to
-        * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
-        * @hide
-        */
+         * If the elapsed realtime between two NITZ signals is greater than this value then the
+         * second signal cannot be ignored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
         @Readable
         public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
 
+        /**
+         * If the device connects to a telephony network and was disconnected from a telephony
+         * network for less than this time, a previously received NITZ signal can be restored.
+         *
+         * <p>This value is in milliseconds. It is used for telephony-based time and time zone
+         * detection.
+         * @hide
+         */
+        public static final String NITZ_NETWORK_DISCONNECT_RETENTION =
+                "nitz_network_disconnect_retention";
+
         /** Preferred NTP server. {@hide} */
         @Readable
         public static final String NTP_SERVER = "ntp_server";
@@ -11516,6 +11558,12 @@
         @Readable
         public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
 
+        /** Timeout for package verification during streaming installations.
+         * @hide */
+        @Readable
+        public static final String PACKAGE_STREAMING_VERIFIER_TIMEOUT =
+                "streaming_verifier_timeout";
+
         /** Timeout for app integrity verification.
          * @hide */
         @Readable
@@ -12063,7 +12111,7 @@
          * Value to specify whether network quality scores and badging should be shown in the UI.
          *
          * Type: int (0 for false, 1 for true)
-         * @deprecated {@code NetworkScoreManager} is deprecated.
+         * @deprecated {@link NetworkScoreManager} is deprecated.
          * @hide
          */
         @Deprecated
@@ -12075,7 +12123,7 @@
          * when generating SSID only bases score curves.
          *
          * Type: long
-         * @deprecated {@code NetworkScoreManager} is deprecated.
+         * @deprecated {@link NetworkScoreManager} is deprecated.
          * @hide
          */
         @Deprecated
@@ -12111,7 +12159,7 @@
          * {@link NetworkScoreManager#setActiveScorer(String)} to write it.
          *
          * Type: string - package name
-         * @deprecated {@code NetworkScoreManager} is deprecated.
+         * @deprecated {@link NetworkScoreManager} is deprecated.
          * @hide
          */
         @Deprecated
@@ -12124,7 +12172,7 @@
          * networks automatically.
          *
          * Type: string package name or null if the feature is either not provided or disabled.
-         * @deprecated {@code NetworkScoreManager} is deprecated.
+         * @deprecated {@link NetworkScoreManager} is deprecated.
          * @hide
          */
         @Deprecated
@@ -12137,7 +12185,7 @@
          * {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
          *
          * Type: long
-         * @deprecated {@code NetworkScoreManager} is deprecated.
+         * @deprecated {@link NetworkScoreManager} is deprecated.
          * @hide
          */
         @Deprecated
@@ -16654,12 +16702,6 @@
                     "alt_bypass_wifi_requirement_time_millis";
 
             /**
-             * Whether or not Up/Down Gestures are enabled.
-             * @hide
-             */
-            public static final String UPDOWN_GESTURES_ENABLED = "updown_gestures_enabled";
-
-            /**
              * Whether the setup was skipped.
              * @hide
              */
@@ -16728,12 +16770,6 @@
             public static final String WEAR_OS_VERSION_STRING = "wear_os_version_string";
 
             /**
-             * If an alternate launcher is enabled.
-             * @hide
-             */
-            public static final String ALTERNATE_LAUNCHER_ENABLED = "alternate_launcher_enabled";
-
-            /**
              * How round the corners of square screens are.
              * @hide
              */
@@ -16968,6 +17004,14 @@
                     "wear_activity_auto_resume_timeout_ms";
 
             /**
+             * If the current {@code WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS} value is set by user.
+             * 1 for true, 0 for false.
+             * @hide
+             */
+            public static final String WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER =
+                    "wear_activity_auto_resume_timeout_set_by_user";
+
+            /**
              * If burn in protection is enabled.
              * @hide
              */
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 50a44a1..e3c3969 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5317,6 +5317,13 @@
         public static final String COLUMN_PROFILE_CLASS = "profile_class";
 
         /**
+         * TelephonyProvider column name for the port index of the active UICC port.
+         * <P>Type: INTEGER (int)</P>
+         * @hide
+         */
+        public static final String COLUMN_PORT_INDEX = "port_index";
+
+        /**
          * A testing profile can be pre-loaded or downloaded onto
          * the eUICC and provides connectivity to test equipment
          * for the purpose of testing the device and the eUICC. It
@@ -5446,5 +5453,12 @@
          */
         public static final String COLUMN_PHONE_NUMBER_SOURCE_IMS =
                 "phone_number_source_ims";
+
+        /**
+         * TelephonyProvider column name for the device's preferred usage setting.
+         *
+         * @hide
+         */
+        public static final String COLUMN_USAGE_SETTING = "usage_setting";
     }
 }
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 3b4f7e2..f900558 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -317,16 +317,35 @@
             ErrorCode.MISSING_MIN_MAC_LENGTH; // -58;
     public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH =
             ErrorCode.UNSUPPORTED_MIN_MAC_LENGTH; // -59;
+    public static final int KM_ERROR_UNSUPPORTED_KDF = ErrorCode.UNSUPPORTED_KDF; // -60
+    public static final int KM_ERROR_UNSUPPORTED_EC_CURVE = ErrorCode.UNSUPPORTED_EC_CURVE; // -61
+    // -62 is KEY_REQUIRES_UPGRADE and is handled by Keystore.
+    public static final int KM_ERROR_ATTESTATION_CHALLENGE_MISSING =
+            ErrorCode.ATTESTATION_CHALLENGE_MISSING; // -63
+    public static final int KM_ERROR_KEYMINT_NOT_CONFIGURED =
+            ErrorCode.KEYMINT_NOT_CONFIGURED; // -64
+    public static final int KM_ERROR_ATTESTATION_APPLICATION_ID_MISSING =
+            ErrorCode.ATTESTATION_APPLICATION_ID_MISSING; // -65;
     public static final int KM_ERROR_CANNOT_ATTEST_IDS =
             ErrorCode.CANNOT_ATTEST_IDS; // -66;
+    public static final int KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE =
+            ErrorCode.ROLLBACK_RESISTANCE_UNAVAILABLE; // -67;
     public static final int KM_ERROR_HARDWARE_TYPE_UNAVAILABLE =
             ErrorCode.HARDWARE_TYPE_UNAVAILABLE; // -68;
     public static final int KM_ERROR_DEVICE_LOCKED =
             ErrorCode.DEVICE_LOCKED; // -72;
+    public static final int KM_ERROR_STORAGE_KEY_UNSUPPORTED =
+            ErrorCode.STORAGE_KEY_UNSUPPORTED; // -77,
+    public static final int KM_ERROR_INCOMPATIBLE_MGF_DIGEST =
+            ErrorCode.INCOMPATIBLE_MGF_DIGEST; // -78,
+    public static final int KM_ERROR_UNSUPPORTED_MGF_DIGEST =
+            ErrorCode.UNSUPPORTED_MGF_DIGEST; // -79,
     public static final int KM_ERROR_MISSING_NOT_BEFORE =
             ErrorCode.MISSING_NOT_BEFORE; // -80;
     public static final int KM_ERROR_MISSING_NOT_AFTER =
             ErrorCode.MISSING_NOT_AFTER; // -80;
+    public static final int KM_ERROR_HARDWARE_NOT_YET_AVAILABLE =
+            ErrorCode.HARDWARE_NOT_YET_AVAILABLE; // -85
     public static final int KM_ERROR_UNIMPLEMENTED =
             ErrorCode.UNIMPLEMENTED; // -100;
     public static final int KM_ERROR_VERSION_MISMATCH =
diff --git a/core/java/android/service/autofill/BatchUpdates.java b/core/java/android/service/autofill/BatchUpdates.java
index 8eeecc2..c996cc0 100644
--- a/core/java/android/service/autofill/BatchUpdates.java
+++ b/core/java/android/service/autofill/BatchUpdates.java
@@ -205,7 +205,7 @@
                     builder.transformChild(ids[i], values[i]);
                 }
             }
-            final RemoteViews updates = parcel.readParcelable(null);
+            final RemoteViews updates = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (updates != null) {
                 builder.updateTemplate(updates);
             }
diff --git a/core/java/android/service/autofill/CompositeUserData.java b/core/java/android/service/autofill/CompositeUserData.java
index 92952cb..55ac5a5 100644
--- a/core/java/android/service/autofill/CompositeUserData.java
+++ b/core/java/android/service/autofill/CompositeUserData.java
@@ -197,8 +197,8 @@
                     // Always go through the builder to ensure the data ingested by
                     // the system obeys the contract of the builder to avoid attacks
                     // using specially crafted parcels.
-                    final UserData genericUserData = parcel.readParcelable(null);
-                    final UserData packageUserData = parcel.readParcelable(null);
+                    final UserData genericUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
+                    final UserData packageUserData = parcel.readParcelable(null, android.service.autofill.UserData.class);
                     return new CompositeUserData(genericUserData, packageUserData);
                 }
 
diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java
index f3f912b..690cd06 100644
--- a/core/java/android/service/autofill/CustomDescription.java
+++ b/core/java/android/service/autofill/CustomDescription.java
@@ -437,7 +437,7 @@
             // Always go through the builder to ensure the data ingested by
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
-            final RemoteViews parentPresentation = parcel.readParcelable(null);
+            final RemoteViews parentPresentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (parentPresentation == null) return null;
 
             final Builder builder = new Builder(parentPresentation);
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 8539bf5..86341a9 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -913,10 +913,10 @@
     public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
         @Override
         public Dataset createFromParcel(Parcel parcel) {
-            final RemoteViews presentation = parcel.readParcelable(null);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             final InlinePresentation inlineTooltipPresentation =
-                    parcel.readParcelable(null);
+                    parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             final ArrayList<AutofillId> ids =
                     parcel.createTypedArrayList(AutofillId.CREATOR);
             final ArrayList<AutofillValue> values =
@@ -929,8 +929,8 @@
                     parcel.createTypedArrayList(InlinePresentation.CREATOR);
             final ArrayList<DatasetFieldFilter> filters =
                     parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
-            final ClipData fieldContent = parcel.readParcelable(null);
-            final IntentSender authentication = parcel.readParcelable(null);
+            final ClipData fieldContent = parcel.readParcelable(null, android.content.ClipData.class);
+            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
             final String datasetId = parcel.readString();
 
             // Always go through the builder to ensure the data ingested by
@@ -1014,7 +1014,7 @@
 
             @Override
             public DatasetFieldFilter createFromParcel(Parcel parcel) {
-                return new DatasetFieldFilter((Pattern) parcel.readSerializable());
+                return new DatasetFieldFilter((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
             }
 
             @Override
diff --git a/core/java/android/service/autofill/DateTransformation.java b/core/java/android/service/autofill/DateTransformation.java
index 7340857..df5ed4da 100644
--- a/core/java/android/service/autofill/DateTransformation.java
+++ b/core/java/android/service/autofill/DateTransformation.java
@@ -114,8 +114,8 @@
             new Parcelable.Creator<DateTransformation>() {
         @Override
         public DateTransformation createFromParcel(Parcel parcel) {
-            return new DateTransformation(parcel.readParcelable(null),
-                    (DateFormat) parcel.readSerializable());
+            return new DateTransformation(parcel.readParcelable(null, android.view.autofill.AutofillId.class),
+                    (DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/DateValueSanitizer.java b/core/java/android/service/autofill/DateValueSanitizer.java
index 6f7808e..c7d5b79 100644
--- a/core/java/android/service/autofill/DateValueSanitizer.java
+++ b/core/java/android/service/autofill/DateValueSanitizer.java
@@ -111,7 +111,7 @@
             new Parcelable.Creator<DateValueSanitizer>() {
         @Override
         public DateValueSanitizer createFromParcel(Parcel parcel) {
-            return new DateValueSanitizer((DateFormat) parcel.readSerializable());
+            return new DateValueSanitizer((DateFormat) parcel.readSerializable(android.icu.text.DateFormat.class.getClassLoader(), android.icu.text.DateFormat.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index af846b6..43bd410 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -384,7 +384,7 @@
         byte flg = in.readByte();
         int id = in.readInt();
         List<FillContext> fillContexts = new ArrayList<>();
-        in.readParcelableList(fillContexts, FillContext.class.getClassLoader());
+        in.readParcelableList(fillContexts, FillContext.class.getClassLoader(), android.service.autofill.FillContext.class);
         Bundle clientState = (flg & 0x4) == 0 ? null : in.readBundle();
         int flags = in.readInt();
         InlineSuggestionsRequest inlineSuggestionsRequest = (flg & 0x10) == 0 ? null : (InlineSuggestionsRequest) in.readTypedObject(InlineSuggestionsRequest.CREATOR);
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 970cb18..d94988e 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -834,35 +834,35 @@
             // the system obeys the contract of the builder to avoid attacks
             // using specially crafted parcels.
             final Builder builder = new Builder();
-            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null);
+            final ParceledListSlice<Dataset> datasetSlice = parcel.readParcelable(null, android.content.pm.ParceledListSlice.class);
             final List<Dataset> datasets = (datasetSlice != null) ? datasetSlice.getList() : null;
             final int datasetCount = (datasets != null) ? datasets.size() : 0;
             for (int i = 0; i < datasetCount; i++) {
                 builder.addDataset(datasets.get(i));
             }
-            builder.setSaveInfo(parcel.readParcelable(null));
-            builder.setClientState(parcel.readParcelable(null));
+            builder.setSaveInfo(parcel.readParcelable(null, android.service.autofill.SaveInfo.class));
+            builder.setClientState(parcel.readParcelable(null, android.os.Bundle.class));
 
             // Sets authentication state.
             final AutofillId[] authenticationIds = parcel.readParcelableArray(null,
                     AutofillId.class);
-            final IntentSender authentication = parcel.readParcelable(null);
-            final RemoteViews presentation = parcel.readParcelable(null);
-            final InlinePresentation inlinePresentation = parcel.readParcelable(null);
-            final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null);
+            final IntentSender authentication = parcel.readParcelable(null, android.content.IntentSender.class);
+            final RemoteViews presentation = parcel.readParcelable(null, android.widget.RemoteViews.class);
+            final InlinePresentation inlinePresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
+            final InlinePresentation inlineTooltipPresentation = parcel.readParcelable(null, android.service.autofill.InlinePresentation.class);
             if (authenticationIds != null) {
                 builder.setAuthentication(authenticationIds, authentication, presentation,
                         inlinePresentation, inlineTooltipPresentation);
             }
-            final RemoteViews header = parcel.readParcelable(null);
+            final RemoteViews header = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (header != null) {
                 builder.setHeader(header);
             }
-            final RemoteViews footer = parcel.readParcelable(null);
+            final RemoteViews footer = parcel.readParcelable(null, android.widget.RemoteViews.class);
             if (footer != null) {
                 builder.setFooter(footer);
             }
-            final UserData userData = parcel.readParcelable(null);
+            final UserData userData = parcel.readParcelable(null, android.service.autofill.UserData.class);
             if (userData != null) {
                 builder.setUserData(userData);
             }
diff --git a/core/java/android/service/autofill/ImageTransformation.java b/core/java/android/service/autofill/ImageTransformation.java
index e317159..af82205 100644
--- a/core/java/android/service/autofill/ImageTransformation.java
+++ b/core/java/android/service/autofill/ImageTransformation.java
@@ -247,7 +247,7 @@
             new Parcelable.Creator<ImageTransformation>() {
         @Override
         public ImageTransformation createFromParcel(Parcel parcel) {
-            final AutofillId id = parcel.readParcelable(null);
+            final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
 
             final Pattern[] regexs = (Pattern[]) parcel.readSerializable();
             final int[] resIds = parcel.createIntArray();
diff --git a/core/java/android/service/autofill/NegationValidator.java b/core/java/android/service/autofill/NegationValidator.java
index d626845..85cd981 100644
--- a/core/java/android/service/autofill/NegationValidator.java
+++ b/core/java/android/service/autofill/NegationValidator.java
@@ -68,7 +68,7 @@
             new Parcelable.Creator<NegationValidator>() {
         @Override
         public NegationValidator createFromParcel(Parcel parcel) {
-            return new NegationValidator(parcel.readParcelable(null));
+            return new NegationValidator(parcel.readParcelable(null, android.service.autofill.InternalValidator.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/RegexValidator.java b/core/java/android/service/autofill/RegexValidator.java
index 00c4347..4c58590 100644
--- a/core/java/android/service/autofill/RegexValidator.java
+++ b/core/java/android/service/autofill/RegexValidator.java
@@ -96,8 +96,8 @@
             new Parcelable.Creator<RegexValidator>() {
         @Override
         public RegexValidator createFromParcel(Parcel parcel) {
-            return new RegexValidator(parcel.readParcelable(null),
-                    (Pattern) parcel.readSerializable());
+            return new RegexValidator(parcel.readParcelable(null, android.view.autofill.AutofillId.class),
+                    (Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class));
         }
 
         @Override
diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java
index 8edfde8..5fe1d4f 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -888,14 +888,14 @@
                 builder.setOptionalIds(optionalIds);
             }
 
-            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null));
+            builder.setNegativeAction(parcel.readInt(), parcel.readParcelable(null, android.content.IntentSender.class));
             builder.setPositiveAction(parcel.readInt());
             builder.setDescription(parcel.readCharSequence());
-            final CustomDescription customDescripton = parcel.readParcelable(null);
+            final CustomDescription customDescripton = parcel.readParcelable(null, android.service.autofill.CustomDescription.class);
             if (customDescripton != null) {
                 builder.setCustomDescription(customDescripton);
             }
-            final InternalValidator validator = parcel.readParcelable(null);
+            final InternalValidator validator = parcel.readParcelable(null, android.service.autofill.InternalValidator.class);
             if (validator != null) {
                 builder.setValidator(validator);
             }
@@ -909,7 +909,7 @@
                     builder.addSanitizer(sanitizers[i], autofillIds);
                 }
             }
-            final AutofillId triggerId = parcel.readParcelable(null);
+            final AutofillId triggerId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
             if (triggerId != null) {
                 builder.setTriggerId(triggerId);
             }
diff --git a/core/java/android/service/autofill/TextValueSanitizer.java b/core/java/android/service/autofill/TextValueSanitizer.java
index 5bafa7a..46c18b2 100644
--- a/core/java/android/service/autofill/TextValueSanitizer.java
+++ b/core/java/android/service/autofill/TextValueSanitizer.java
@@ -119,7 +119,7 @@
             new Parcelable.Creator<TextValueSanitizer>() {
         @Override
         public TextValueSanitizer createFromParcel(Parcel parcel) {
-            return new TextValueSanitizer((Pattern) parcel.readSerializable(), parcel.readString());
+            return new TextValueSanitizer((Pattern) parcel.readSerializable(java.util.regex.Pattern.class.getClassLoader(), java.util.regex.Pattern.class), parcel.readString());
         }
 
         @Override
diff --git a/core/java/android/service/cloudsearch/OWNERS b/core/java/android/service/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/core/java/android/service/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/core/java/android/service/contentcapture/ActivityEvent.java b/core/java/android/service/contentcapture/ActivityEvent.java
index 74a7355..d286942 100644
--- a/core/java/android/service/contentcapture/ActivityEvent.java
+++ b/core/java/android/service/contentcapture/ActivityEvent.java
@@ -149,7 +149,7 @@
         @Override
         @NonNull
         public ActivityEvent createFromParcel(@NonNull Parcel parcel) {
-            final ComponentName componentName = parcel.readParcelable(null);
+            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
             final int eventType = parcel.readInt();
             return new ActivityEvent(componentName, eventType);
         }
diff --git a/core/java/android/service/contentcapture/SnapshotData.java b/core/java/android/service/contentcapture/SnapshotData.java
index bf469b4..f72624d 100644
--- a/core/java/android/service/contentcapture/SnapshotData.java
+++ b/core/java/android/service/contentcapture/SnapshotData.java
@@ -51,8 +51,8 @@
 
     SnapshotData(@NonNull Parcel parcel) {
         mAssistData = parcel.readBundle();
-        mAssistStructure = parcel.readParcelable(null);
-        mAssistContent = parcel.readParcelable(null);
+        mAssistStructure = parcel.readParcelable(null, android.app.assist.AssistStructure.class);
+        mAssistContent = parcel.readParcelable(null, android.app.assist.AssistContent.class);
     }
 
     /**
diff --git a/core/java/android/service/games/GameService.java b/core/java/android/service/games/GameService.java
new file mode 100644
index 0000000..4b440dd
--- /dev/null
+++ b/core/java/android/service/games/GameService.java
@@ -0,0 +1,118 @@
+/*
+ * 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.service.games;
+
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.IGameManagerService;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.Objects;
+
+/**
+ * Top-level service of the game service, which provides support for determining
+ * when a game session should begin. It is always kept running by the system.
+ * Because of this it should be kept as lightweight as possible.
+ *
+ * Heavy weight operations (such as showing UI) should be implemented in the
+ * associated {@link GameSessionService} when a game session is taking place. Its
+ * implementation should run in a separate process from the {@link GameService}.
+ *
+ * @hide
+ */
+@SystemApi
+public class GameService extends Service {
+    static final String TAG = "GameService";
+
+    /**
+     * 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_GAME_SERVICE} permission so
+     * that other applications can not abuse it.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE =
+            "android.service.games.GameService";
+
+    private IGameManagerService mGameManagerService;
+    private final IGameService mInterface = new IGameService.Stub() {
+        @Override
+        public void connected() {
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    GameService::doOnConnected, GameService.this));
+        }
+
+        @Override
+        public void disconnected() {
+            Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                    GameService::onDisconnected, GameService.this));
+        }
+    };
+    private final IBinder.DeathRecipient mGameManagerServiceDeathRecipient = () -> {
+        Log.w(TAG, "System service binder died. Shutting down");
+
+        Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage(
+                GameService::onDisconnected, GameService.this));
+    };
+
+    @Override
+    @Nullable
+    public IBinder onBind(@Nullable Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        return null;
+    }
+
+    private void doOnConnected() {
+        mGameManagerService =
+                IGameManagerService.Stub.asInterface(
+                        ServiceManager.getService(Context.GAME_SERVICE));
+        Objects.requireNonNull(mGameManagerService);
+        try {
+            mGameManagerService.asBinder().linkToDeath(mGameManagerServiceDeathRecipient, 0);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Unable to link to death with system service");
+        }
+
+        onConnected();
+    }
+
+    /**
+     * Called during service initialization to indicate that the system is ready
+     * to receive interaction from it. You should generally do initialization here
+     * rather than in {@link #onCreate}.
+     */
+    public void onConnected() {}
+
+    /**
+     * Called during service de-initialization to indicate that the system is shutting the
+     * service down. At this point this service may no longer be the active {@link GameService}.
+     * The service should clean up any resources that it holds at this point.
+     */
+    public void onDisconnected() {}
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/service/games/IGameService.aidl
similarity index 82%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/service/games/IGameService.aidl
index 0abc7ec..8a0d636 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/service/games/IGameService.aidl
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.service.games;
 
-parcelable TsRequest;
+/**
+ * @hide
+ */
+oneway interface IGameService {
+    void connected();
+    void disconnected();
+}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 4f324f9..267b2ff 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -114,7 +114,7 @@
     }
 
     public Condition(Parcel source) {
-        this((Uri)source.readParcelable(Condition.class.getClassLoader()),
+        this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class),
                 source.readString(),
                 source.readString(),
                 source.readString(),
diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java
index 3d0984c..35b6bad 100644
--- a/core/java/android/service/notification/ConversationChannelWrapper.java
+++ b/core/java/android/service/notification/ConversationChannelWrapper.java
@@ -40,10 +40,10 @@
     public ConversationChannelWrapper() {}
 
     protected ConversationChannelWrapper(Parcel in) {
-        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader());
+        mNotificationChannel = in.readParcelable(NotificationChannel.class.getClassLoader(), android.app.NotificationChannel.class);
         mGroupLabel = in.readCharSequence();
         mParentChannelLabel = in.readCharSequence();
-        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader(), android.content.pm.ShortcutInfo.class);
         mPkg = in.readStringNoHelper();
         mUid = in.readInt();
     }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c945954..ae39d3d 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1763,7 +1763,7 @@
             mImportanceExplanation = in.readCharSequence(); // may be null
             mRankingScore = in.readFloat();
             mOverrideGroupKey = in.readString(); // may be null
-            mChannel = in.readParcelable(cl); // may be null
+            mChannel = in.readParcelable(cl, android.app.NotificationChannel.class); // may be null
             mOverridePeople = in.createStringArrayList();
             mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR);
             mShowBadge = in.readBoolean();
@@ -1776,7 +1776,7 @@
             mCanBubble = in.readBoolean();
             mIsTextChanged = in.readBoolean();
             mIsConversation = in.readBoolean();
-            mShortcutInfo = in.readParcelable(cl);
+            mShortcutInfo = in.readParcelable(cl, android.content.pm.ShortcutInfo.class);
             mRankingAdjustment = in.readInt();
             mIsBubble = in.readBoolean();
         }
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c64f4c46..a853714 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -30,7 +30,7 @@
     }
 
     public NotificationRankingUpdate(Parcel in) {
-        mRankingMap = in.readParcelable(getClass().getClassLoader());
+        mRankingMap = in.readParcelable(getClass().getClassLoader(), android.service.notification.NotificationListenerService.RankingMap.class);
     }
 
     public NotificationListenerService.RankingMap getRankingMap() {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index c1d5a28..8834cee 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -211,7 +211,7 @@
         allowCallsFrom = source.readInt();
         allowMessagesFrom = source.readInt();
         user = source.readInt();
-        manualRule = source.readParcelable(null);
+        manualRule = source.readParcelable(null, android.service.notification.ZenModeConfig.ZenRule.class);
         final int len = source.readInt();
         if (len > 0) {
             final String[] ids = new String[len];
@@ -1800,10 +1800,10 @@
                 name = source.readString();
             }
             zenMode = source.readInt();
-            conditionId = source.readParcelable(null);
-            condition = source.readParcelable(null);
-            component = source.readParcelable(null);
-            configurationActivity = source.readParcelable(null);
+            conditionId = source.readParcelable(null, android.net.Uri.class);
+            condition = source.readParcelable(null, android.service.notification.Condition.class);
+            component = source.readParcelable(null, android.content.ComponentName.class);
+            configurationActivity = source.readParcelable(null, android.content.ComponentName.class);
             if (source.readInt() == 1) {
                 id = source.readString();
             }
@@ -1811,7 +1811,7 @@
             if (source.readInt() == 1) {
                 enabler = source.readString();
             }
-            zenPolicy = source.readParcelable(null);
+            zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
             modified = source.readInt() == 1;
             pkg = source.readString();
         }
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index ed3a9ac..a04f073 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -804,8 +804,8 @@
         @Override
         public ZenPolicy createFromParcel(Parcel source) {
             ZenPolicy policy = new ZenPolicy();
-            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader());
-            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader());
+            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
             policy.mConversationSenders = source.readInt();
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
index 0551e27..7471a4f 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsResponse.java
@@ -63,7 +63,7 @@
     private static GetWalletCardsResponse readFromParcel(Parcel source) {
         int size = source.readInt();
         List<WalletCard> walletCards =
-                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader());
+                source.readParcelableList(new ArrayList<>(size), WalletCard.class.getClassLoader(), android.service.quickaccesswallet.WalletCard.class);
         int selectedIndex = source.readInt();
         return new GetWalletCardsResponse(walletCards, selectedIndex);
     }
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
new file mode 100644
index 0000000..2bd99ac
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
@@ -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 android.service.selectiontoolbar;
+
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+
+/**
+ * The service to render the selection toolbar menus.
+ *
+ * @hide
+ */
+oneway interface ISelectionToolbarRenderService {
+    void onShow(in ShowInfo showInfo, in ISelectionToolbarCallback callback);
+    void onHide(long widgetToken);
+    void onDismiss(long widgetToken);
+}
diff --git a/core/java/android/service/selectiontoolbar/OWNERS b/core/java/android/service/selectiontoolbar/OWNERS
new file mode 100644
index 0000000..5500b92
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 709498
+
+augale@google.com
+joannechung@google.com
+licha@google.com
+lpeter@google.com
+svetoslavganov@google.com
+toki@google.com
+tonymak@google.com
+tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
new file mode 100644
index 0000000..6468183
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
@@ -0,0 +1,53 @@
+/*
+ * 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.service.selectiontoolbar;
+
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
+
+/**
+ * The callback that the render service uses to communicate with the host of the selection toolbar
+ * container.
+ *
+ * @hide
+ */
+public interface SelectionToolbarRenderCallback {
+    /**
+     * The selection toolbar is shown.
+     */
+    void onShown(WidgetInfo widgetInfo);
+    /**
+     * The selection toolbar is hidden.
+     */
+    void onHidden(long widgetToken);
+    /**
+     * The selection toolbar is dismissed.
+     */
+    void onDismissed(long widgetToken);
+    /**
+     * The selection toolbar has changed.
+     */
+    void onWidgetUpdated(WidgetInfo info);
+    /**
+     * The menu item on the selection toolbar has been clicked.
+     */
+    void onMenuItemClicked(ToolbarMenuItem item);
+    /**
+     * The error occurred when operating on the selection toolbar.
+     */
+    void onError(int errorCode);
+}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
new file mode 100644
index 0000000..6f66c9f
--- /dev/null
+++ b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
@@ -0,0 +1,182 @@
+/*
+ * 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.service.selectiontoolbar;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
+
+/**
+ * Service for rendering selection toolbar.
+ *
+ * @hide
+ */
+public abstract class SelectionToolbarRenderService extends Service {
+
+    private static final String TAG = "SelectionToolbarRenderService";
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     *
+     * <p>To be supported, the service must also require the
+     * {@link android.Manifest.permission#BIND_SELECTION_TOOLBAR_RENDER_SERVICE} permission so
+     * that other applications can not abuse it.
+     */
+    public static final String SERVICE_INTERFACE =
+            "android.service.selectiontoolbar.SelectionToolbarRenderService";
+
+    private Handler mHandler;
+
+    /**
+     * Binder to receive calls from system server.
+     */
+    private final ISelectionToolbarRenderService mInterface =
+            new ISelectionToolbarRenderService.Stub() {
+
+        @Override
+        public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) {
+            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow,
+                    SelectionToolbarRenderService.this, showInfo,
+                    new RemoteCallbackWrapper(callback)));
+        }
+
+        @Override
+        public void onHide(long widgetToken) {
+            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onHide,
+                    SelectionToolbarRenderService.this, widgetToken));
+        }
+
+        @Override
+        public void onDismiss(long widgetToken) {
+            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss,
+                    SelectionToolbarRenderService.this, widgetToken));
+        }
+    };
+
+    @CallSuper
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+    }
+
+    @Override
+    @Nullable
+    public final IBinder onBind(@NonNull Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            return mInterface.asBinder();
+        }
+        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
+        return null;
+    }
+
+
+    /**
+     * Called when showing the selection toolbar.
+     */
+    public abstract void onShow(ShowInfo showInfo, RemoteCallbackWrapper callbackWrapper);
+
+    /**
+     * Called when hiding the selection toolbar.
+     */
+    public abstract void onHide(long widgetToken);
+
+
+    /**
+     * Called when dismissing the selection toolbar.
+     */
+    public abstract void onDismiss(long widgetToken);
+
+    /**
+     * Add avadoc.
+     */
+    public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback {
+
+        private final ISelectionToolbarCallback mRemoteCallback;
+
+        RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) {
+            mRemoteCallback = remoteCallback;
+        }
+
+        @Override
+        public void onShown(WidgetInfo widgetInfo) {
+            try {
+                mRemoteCallback.onShown(widgetInfo);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onHidden(long widgetToken) {
+            try {
+                mRemoteCallback.onHidden(widgetToken);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onDismissed(long widgetToken) {
+            try {
+                mRemoteCallback.onDismissed(widgetToken);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onWidgetUpdated(WidgetInfo widgetInfo) {
+            try {
+                mRemoteCallback.onWidgetUpdated(widgetInfo);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onMenuItemClicked(ToolbarMenuItem item) {
+            try {
+                mRemoteCallback.onMenuItemClicked(item);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+
+        @Override
+        public void onError(int errorCode) {
+            try {
+                mRemoteCallback.onError(errorCode);
+            } catch (RemoteException e) {
+                e.rethrowAsRuntimeException();
+            }
+        }
+    }
+}
diff --git a/core/java/android/service/settings/suggestions/Suggestion.java b/core/java/android/service/settings/suggestions/Suggestion.java
index 3e63efb..16622d7 100644
--- a/core/java/android/service/settings/suggestions/Suggestion.java
+++ b/core/java/android/service/settings/suggestions/Suggestion.java
@@ -120,9 +120,9 @@
         mId = in.readString();
         mTitle = in.readCharSequence();
         mSummary = in.readCharSequence();
-        mIcon = in.readParcelable(Icon.class.getClassLoader());
+        mIcon = in.readParcelable(Icon.class.getClassLoader(), android.graphics.drawable.Icon.class);
         mFlags = in.readInt();
-        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+        mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(), android.app.PendingIntent.class);
     }
 
     public static final @android.annotation.NonNull Creator<Suggestion> CREATOR = new Creator<Suggestion>() {
diff --git a/core/java/android/service/timezone/TimeZoneProviderEvent.java b/core/java/android/service/timezone/TimeZoneProviderEvent.java
index 7005281..f6433b7 100644
--- a/core/java/android/service/timezone/TimeZoneProviderEvent.java
+++ b/core/java/android/service/timezone/TimeZoneProviderEvent.java
@@ -141,7 +141,7 @@
                     int type = in.readInt();
                     long creationElapsedMillis = in.readLong();
                     TimeZoneProviderSuggestion suggestion =
-                            in.readParcelable(getClass().getClassLoader());
+                            in.readParcelable(getClass().getClassLoader(), android.service.timezone.TimeZoneProviderSuggestion.class);
                     String failureCause = in.readString8();
                     return new TimeZoneProviderEvent(
                             type, creationElapsedMillis, suggestion, failureCause);
diff --git a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
index 229fa26..4841ac1 100644
--- a/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
+++ b/core/java/android/service/timezone/TimeZoneProviderSuggestion.java
@@ -100,7 +100,7 @@
                 public TimeZoneProviderSuggestion createFromParcel(Parcel in) {
                     @SuppressWarnings("unchecked")
                     ArrayList<String> timeZoneIds =
-                            (ArrayList<String>) in.readArrayList(null /* classLoader */);
+                            (ArrayList<String>) in.readArrayList(null /* classLoader */, java.lang.String.class);
                     long elapsedRealtimeMillis = in.readLong();
                     return new TimeZoneProviderSuggestion(timeZoneIds, elapsedRealtimeMillis);
                 }
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 65a857e..e64d685 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -1749,8 +1749,9 @@
     /**
      * Called when there has been a failure transferring the {@link AssistStructure} to
      * the assistant.  This may happen, for example, if the data is too large and results
-     * in an out of memory exception, or the client has provided corrupt data.  This will
-     * be called immediately before {@link #onHandleAssist} and the AssistStructure supplied
+     * in an out of memory exception, the data has been cleared during transferring due to
+     * the new incoming assist data, or the client has provided corrupt data. This will be
+     * called immediately before {@link #onHandleAssist} and the AssistStructure supplied
      * there afterwards will be null.
      *
      * @param failure The failure exception that was thrown when building the
@@ -1788,7 +1789,8 @@
      * Called to receive data from the application that the user was currently viewing when
      * an assist session is started. If the original show request did not specify
      * {@link #SHOW_WITH_ASSIST}, {@link AssistState} parameter will only provide
-     * {@link ActivityId}.
+     * {@link ActivityId}. If there was a failure to write the assist data to
+     * {@link AssistStructure}, the {@link AssistState#getAssistStructure()} will return null.
      *
      * <p>This method is called for all activities along with an index and count that indicates
      * which activity the data is for. {@code index} will be between 0 and {@code count}-1 and
@@ -2213,7 +2215,8 @@
          * @return If available, the structure definition of all windows currently
          * displayed by the app. May be null if assist data has been disabled by the user
          * or device policy; will be null if the original show request did not specify
-         * {@link #SHOW_WITH_ASSIST}; will be an empty stub if the application has disabled assist
+         * {@link #SHOW_WITH_ASSIST} or the assist data has been corrupt when writing the data to
+         * {@link AssistStructure}; will be an empty stub if the application has disabled assist
          * by marking its window as secure.
          */
         public @Nullable AssistStructure getAssistStructure() {
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index b862ec1..73ffd66 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -44,7 +44,6 @@
 import android.graphics.BLASTBufferQueue;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
@@ -609,6 +608,18 @@
             }
         }
 
+        /** @hide */
+        public void setShowForAllUsers(boolean show) {
+            mWindowPrivateFlags = show
+                    ? (mWindowPrivateFlags
+                        | WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+                    : (mWindowPrivateFlags
+                        & ~WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
+            if (mCreated) {
+                updateSurface(false, false, false);
+            }
+        }
+
         /** {@hide} */
         @UnsupportedAppUsage
         public void setFixedSizeAllowed(boolean allowed) {
@@ -1918,9 +1929,7 @@
 
             mScreenshotSize.set(mSurfaceSize.x, mSurfaceSize.y);
 
-            GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(hardwareBuffer);
-
-            t.setBuffer(mScreenshotSurfaceControl, graphicBuffer);
+            t.setBuffer(mScreenshotSurfaceControl, hardwareBuffer);
             t.setColorSpace(mScreenshotSurfaceControl, screenshotBuffer.getColorSpace());
             // Place on top everything else.
             t.setLayer(mScreenshotSurfaceControl, Integer.MAX_VALUE);
diff --git a/core/java/android/speech/tts/Voice.java b/core/java/android/speech/tts/Voice.java
index 7ffe5eb..0d98a6c 100644
--- a/core/java/android/speech/tts/Voice.java
+++ b/core/java/android/speech/tts/Voice.java
@@ -84,7 +84,7 @@
 
     private Voice(Parcel in) {
         this.mName = in.readString();
-        this.mLocale = (Locale)in.readSerializable();
+        this.mLocale = (Locale)in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
         this.mQuality = in.readInt();
         this.mLatency = in.readInt();
         this.mRequiresNetworkConnection = (in.readByte() == 1);
diff --git a/core/java/android/telephony/SubscriptionPlan.java b/core/java/android/telephony/SubscriptionPlan.java
index d5ac436..fb2d771 100644
--- a/core/java/android/telephony/SubscriptionPlan.java
+++ b/core/java/android/telephony/SubscriptionPlan.java
@@ -99,7 +99,7 @@
     }
 
     private SubscriptionPlan(Parcel source) {
-        cycleRule = source.readParcelable(null);
+        cycleRule = source.readParcelable(null, android.util.RecurrenceRule.class);
         title = source.readCharSequence();
         summary = source.readCharSequence();
         dataLimitBytes = source.readLong();
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index cb1cff9..9eaaa91 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -36,18 +36,24 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SimActivationState;
 import android.telephony.Annotation.SrvccState;
+import android.telephony.TelephonyManager.CarrierPrivilegesListener;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.listeners.ListenerExecutor;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 
+import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
 
 /**
@@ -247,8 +253,8 @@
             } else if (listener.mSubId != null) {
                 subId = listener.mSubId;
             }
-            sRegistry.listenWithEventList(
-                    subId, pkg, featureId, listener.callback, eventsList, notifyNow);
+            sRegistry.listenWithEventList(false, false, subId, pkg, featureId,
+                    listener.callback, eventsList, notifyNow);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -263,11 +269,13 @@
      * @param events List events
      * @param notifyNow Whether to notify instantly
      */
-    private void listenFromCallback(int subId, @NonNull String pkg, @NonNull String featureId,
+    private void listenFromCallback(boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess, int subId,
+            @NonNull String pkg, @NonNull String featureId,
             @NonNull TelephonyCallback telephonyCallback, @NonNull int[] events,
             boolean notifyNow) {
         try {
-            sRegistry.listenWithEventList(
+            sRegistry.listenWithEventList(renounceFineLocationAccess, renounceCoarseLocationAccess,
                     subId, pkg, featureId, telephonyCallback.callback, events, notifyNow);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -283,6 +291,8 @@
      * UI. There is no timeout associated with showing this UX, so a carrier app must be sure to
      * call with active set to false sometime after calling with it set to {@code true}.
      * <p>
+     * This will apply to all subscriptions the carrier app has carrier privileges on.
+     * <p>
      * Requires Permission: calling app has carrier privileges.
      *
      * @param active Whether the carrier network change is or shortly will be
@@ -298,6 +308,31 @@
     }
 
     /**
+     * Informs the system of an intentional upcoming carrier network change by a carrier app on the
+     * given {@code subscriptionId}. This call only used to allow the system to provide alternative
+     * UI while telephony is performing an action that may result in intentional, temporary network
+     * lack of connectivity.
+     * <p>
+     * Based on the active parameter passed in, this method will either show or hide the
+     * alternative UI. There is no timeout associated with showing this UX, so a carrier app must be
+     * sure to call with active set to false sometime after calling with it set to {@code true}.
+     * <p>
+     * Requires Permission: calling app has carrier privileges.
+     *
+     * @param subscriptionId the subscription of the carrier network.
+     * @param active whether the carrier network change is or shortly will be active. Set this value
+     *              to true to begin showing alternative UI and false to stop.
+     * @see TelephonyManager#hasCarrierPrivileges
+     */
+    public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
+        try {
+            sRegistry.notifyCarrierNetworkChangeWithSubId(subscriptionId, active);
+        } catch (RemoteException ex) {
+            // system server crash
+        }
+    }
+
+    /**
      * Notify call state changed on certain subscription.
      *
      * @param slotIndex for which call state changed. Can be derived from subId except when subId is
@@ -1161,14 +1196,17 @@
      *
      * @param callback The {@link TelephonyCallback} object to register.
      */
-    public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor,
+    public void registerTelephonyCallback(boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess,
+            @NonNull @CallbackExecutor Executor executor,
             int subId, String pkgName, String attributionTag, @NonNull TelephonyCallback callback,
             boolean notifyNow) {
         if (callback == null) {
             throw new IllegalStateException("telephony service is null.");
         }
         callback.init(executor);
-        listenFromCallback(subId, pkgName, attributionTag, callback,
+        listenFromCallback(renounceFineLocationAccess, renounceCoarseLocationAccess, subId,
+                pkgName, attributionTag, callback,
                 getEventsFromCallback(callback).stream().mapToInt(i -> i).toArray(), notifyNow);
     }
 
@@ -1179,6 +1217,120 @@
      */
     public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag,
             @NonNull TelephonyCallback callback, boolean notifyNow) {
-        listenFromCallback(subId, pkgName, attributionTag, callback, new int[0], notifyNow);
+        listenFromCallback(false, false, subId,
+                pkgName, attributionTag, callback, new int[0], notifyNow);
+    }
+
+    private static class CarrierPrivilegesListenerWrapper extends ICarrierPrivilegesListener.Stub
+            implements ListenerExecutor {
+        private final WeakReference<CarrierPrivilegesListener> mListener;
+        private final Executor mExecutor;
+
+        CarrierPrivilegesListenerWrapper(CarrierPrivilegesListener listener, Executor executor) {
+            mListener = new WeakReference<>(listener);
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCarrierPrivilegesChanged(
+                List<String> privilegedPackageNames, int[] privilegedUids) {
+            Binder.withCleanCallingIdentity(
+                    () ->
+                            executeSafely(
+                                    mExecutor,
+                                    mListener::get,
+                                    cpl ->
+                                            cpl.onCarrierPrivilegesChanged(
+                                                    privilegedPackageNames, privilegedUids)));
+        }
+    }
+
+    @GuardedBy("sCarrierPrivilegeListeners")
+    private static final WeakHashMap<
+                    CarrierPrivilegesListener, WeakReference<CarrierPrivilegesListenerWrapper>>
+            sCarrierPrivilegeListeners = new WeakHashMap<>();
+
+    /**
+     * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
+     * receive callbacks when the set of packages with carrier privileges changes. The callback will
+     * immediately be called with the latest state.
+     *
+     * @param logicalSlotIndex The SIM slot to listen on
+     * @param executor The executor where {@code listener} will be invoked
+     * @param listener The callback to register
+     */
+    public void addCarrierPrivilegesListener(
+            int logicalSlotIndex,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierPrivilegesListener listener) {
+        if (listener == null || executor == null) {
+            throw new IllegalArgumentException("listener and executor must be non-null");
+        }
+        synchronized (sCarrierPrivilegeListeners) {
+            WeakReference<CarrierPrivilegesListenerWrapper> existing =
+                    sCarrierPrivilegeListeners.get(listener);
+            if (existing != null && existing.get() != null) {
+                Log.d(TAG, "addCarrierPrivilegesListener: listener already registered");
+                return;
+            }
+            CarrierPrivilegesListenerWrapper wrapper =
+                    new CarrierPrivilegesListenerWrapper(listener, executor);
+            sCarrierPrivilegeListeners.put(listener, new WeakReference<>(wrapper));
+            try {
+                sRegistry.addCarrierPrivilegesListener(
+                        logicalSlotIndex,
+                        wrapper,
+                        mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters a {@link CarrierPrivilegesListener}.
+     *
+     * @param listener The callback to unregister
+     */
+    public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must be non-null");
+        }
+        synchronized (sCarrierPrivilegeListeners) {
+            WeakReference<CarrierPrivilegesListenerWrapper> ref =
+                    sCarrierPrivilegeListeners.remove(listener);
+            if (ref == null) return;
+            CarrierPrivilegesListenerWrapper wrapper = ref.get();
+            if (wrapper == null) return;
+            try {
+                sRegistry.removeCarrierPrivilegesListener(wrapper, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Notify listeners that the set of packages with carrier privileges has changed.
+     *
+     * @param logicalSlotIndex The SIM slot the change occurred on
+     * @param privilegedPackageNames The updated set of packages names with carrier privileges
+     * @param privilegedUids The updated set of UIDs with carrier privileges
+     */
+    public void notifyCarrierPrivilegesChanged(
+            int logicalSlotIndex,
+            @NonNull List<String> privilegedPackageNames,
+            @NonNull int[] privilegedUids) {
+        if (privilegedPackageNames == null || privilegedUids == null) {
+            throw new IllegalArgumentException(
+                    "privilegedPackageNames and privilegedUids must be non-null");
+        }
+        try {
+            sRegistry.notifyCarrierPrivilegesChanged(
+                    logicalSlotIndex, privilegedPackageNames, privilegedUids);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 }
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 2f7fb2f..32b3bc6 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -143,9 +143,9 @@
         @Override
         public FontConfig createFromParcel(Parcel source) {
             List<FontFamily> families = source.readParcelableList(new ArrayList<>(),
-                    FontFamily.class.getClassLoader());
+                    FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class);
             List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
-                    Alias.class.getClassLoader());
+                    Alias.class.getClassLoader(), android.text.FontConfig.Alias.class);
             long lastModifiedDate = source.readLong();
             int configVersion = source.readInt();
             return new FontConfig(families, aliases, lastModifiedDate, configVersion);
@@ -617,7 +617,7 @@
             @Override
             public FontFamily createFromParcel(Parcel source) {
                 List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader());
+                        new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class);
                 String name = source.readString8();
                 String langTags = source.readString8();
                 int variant = source.readInt();
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index b80b01f..ae12132 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -858,9 +858,17 @@
         } else if (tag.equalsIgnoreCase("span")) {
             endCssStyle(mSpannableStringBuilder);
         } else if (tag.equalsIgnoreCase("strong")) {
-            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
+            Application application = ActivityThread.currentApplication();
+            int fontWeightAdjustment =
+                    application.getResources().getConfiguration().fontWeightAdjustment;
+            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD,
+                    fontWeightAdjustment));
         } else if (tag.equalsIgnoreCase("b")) {
-            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
+            Application application = ActivityThread.currentApplication();
+            int fontWeightAdjustment =
+                    application.getResources().getConfiguration().fontWeightAdjustment;
+            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD,
+                    fontWeightAdjustment));
         } else if (tag.equalsIgnoreCase("em")) {
             end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
         } else if (tag.equalsIgnoreCase("cite")) {
@@ -1028,8 +1036,11 @@
         // Their ranges should not include the newlines at the end
         Heading h = getLast(text, Heading.class);
         if (h != null) {
+            Application application = ActivityThread.currentApplication();
+            int fontWeightAdjustment =
+                    application.getResources().getConfiguration().fontWeightAdjustment;
             setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]),
-                    new StyleSpan(Typeface.BOLD));
+                    new StyleSpan(Typeface.BOLD, fontWeightAdjustment));
         }
 
         endBlockElement(text);
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index 7f903b6..dfe4119 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -16,6 +16,12 @@
 
 package android.text;
 
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextAttribute.TextAttributeBuilder;
+
+import java.util.List;
+
 /**
  * Bit definitions for an integer defining the basic content type of text
  * held in an {@link Editable} object. Supported classes may be combined
@@ -188,6 +194,21 @@
      */
     public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;
 
+    /**
+     * Flag for {@link #TYPE_CLASS_TEXT}: Let the IME know the text conversion suggestions are
+     * required by the application. Text conversion suggestion is for the transliteration languages
+     * which has pronunciation characters and target characters. When the user is typing the
+     * pronunciation charactes, the IME could provide the possible target characters to the user.
+     * When this flag is set, the IME should insert the text conversion suggestions through
+     * {@link TextAttributeBuilder#setTextConversionSuggestions(List)} and
+     * the {@link TextAttribute} with initialized with the text conversion suggestions is provided
+     * by the IME to the application. To receive the additional information, the application needs
+     * to implement {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)},
+     * {@link InputConnection#setComposingRegion(int, int, TextAttribute)}, and
+     * {@link InputConnection#commitText(CharSequence, int, TextAttribute)}.
+     */
+    public static final int TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 0x00100000;
+
     // ----------------------------------------------------------------------
 
     /**
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6984e4d..4789231 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -874,6 +874,18 @@
                             ? Math.max(fmDescent, Math.round(descents[breakIndex]))
                             : fmDescent;
 
+                    // The fallback ascent/descent may be larger than top/bottom of the default font
+                    // metrics. Adjust top/bottom with ascent/descent for avoiding unexpected
+                    // clipping.
+                    if (fallbackLineSpacing) {
+                        if (ascent < fmTop) {
+                            fmTop = ascent;
+                        }
+                        if (descent > fmBottom) {
+                            fmBottom = descent;
+                        }
+                    }
+
                     v = out(source, here, endPos,
                             ascent, descent, fmTop, fmBottom,
                             v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index ccccdcf..3da8333 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -82,7 +82,7 @@
      * Constructor called from {@link TextUtils} to restore the span.
      */
     public EasyEditSpan(@NonNull Parcel source) {
-        mPendingIntent = source.readParcelable(null);
+        mPendingIntent = source.readParcelable(null, android.app.PendingIntent.class);
         mDeleteEnabled = (source.readByte() == 1);
     }
 
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index bdfa700..176a068 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -17,8 +17,10 @@
 package android.text.style;
 
 import android.annotation.NonNull;
+import android.content.res.Configuration;
 import android.graphics.Paint;
 import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
 import android.os.Parcel;
 import android.text.ParcelableSpan;
 import android.text.TextPaint;
@@ -45,6 +47,7 @@
 public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
 
     private final int mStyle;
+    private final int mFontWeightAdjustment;
 
     /**
      * Creates a {@link StyleSpan} from a style.
@@ -54,7 +57,24 @@
      *              in {@link Typeface}.
      */
     public StyleSpan(int style) {
+        this(style, Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED);
+    }
+
+    /**
+     * Creates a {@link StyleSpan} from a style and font weight adjustment.
+     *
+     * @param style An integer constant describing the style for this span. Examples
+     *              include bold, italic, and normal. Values are constants defined
+     *              in {@link Typeface}.
+     * @param fontWeightAdjustment An integer describing the adjustment to be made to the font
+     *              weight. This is added to the value of the current weight returned by
+     *              {@link Typeface#getWeight()}.
+     * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight
+     * that is used to reflect the current user's preference for increasing font weight.
+     */
+    public StyleSpan(@Typeface.Style int style, int fontWeightAdjustment) {
         mStyle = style;
+        mFontWeightAdjustment = fontWeightAdjustment;
     }
 
     /**
@@ -64,6 +84,7 @@
      */
     public StyleSpan(@NonNull Parcel src) {
         mStyle = src.readInt();
+        mFontWeightAdjustment = src.readInt();
     }
 
     @Override
@@ -91,6 +112,7 @@
     @Override
     public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
         dest.writeInt(mStyle);
+        dest.writeInt(mFontWeightAdjustment);
     }
 
     /**
@@ -100,17 +122,27 @@
         return mStyle;
     }
 
+    /**
+     * Returns the font weight adjustment specified by this span.
+     * <p>
+     * This can be {@link Configuration#FONT_WEIGHT_ADJUSTMENT_UNDEFINED}. This is added to the
+     * value of the current weight returned by {@link Typeface#getWeight()}.
+     */
+    public int getFontWeightAdjustment() {
+        return mFontWeightAdjustment;
+    }
+
     @Override
     public void updateDrawState(TextPaint ds) {
-        apply(ds, mStyle);
+        apply(ds, mStyle, mFontWeightAdjustment);
     }
 
     @Override
     public void updateMeasureState(TextPaint paint) {
-        apply(paint, mStyle);
+        apply(paint, mStyle, mFontWeightAdjustment);
     }
 
-    private static void apply(Paint paint, int style) {
+    private static void apply(Paint paint, int style, int fontWeightAdjustment) {
         int oldStyle;
 
         Typeface old = paint.getTypeface();
@@ -129,6 +161,18 @@
             tf = Typeface.create(old, want);
         }
 
+        // Base typeface may already be bolded by auto bold. Bold further.
+        if ((style & Typeface.BOLD) != 0) {
+            if (fontWeightAdjustment != 0
+                    && fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+                int newWeight = Math.min(
+                        Math.max(tf.getWeight() + fontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
+                        FontStyle.FONT_WEIGHT_MAX);
+                boolean italic = (want & Typeface.ITALIC) != 0;
+                tf = Typeface.create(tf, newWeight, italic);
+            }
+        }
+
         int fake = want & ~tf.getStyle();
 
         if ((fake & Typeface.BOLD) != 0) {
diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java
index 2b04a7a..1eee99a 100644
--- a/core/java/android/text/style/SuggestionRangeSpan.java
+++ b/core/java/android/text/style/SuggestionRangeSpan.java
@@ -16,8 +16,9 @@
 
 package android.text.style;
 
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.text.ParcelableSpan;
 import android.text.TextPaint;
 import android.text.TextUtils;
@@ -25,30 +26,40 @@
 /**
  * A SuggestionRangeSpan is used to show which part of an EditText is affected by a suggestion
  * popup window.
- *
- * @hide
  */
-public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan {
+public final class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan {
     private int mBackgroundColor;
 
-    @UnsupportedAppUsage
     public SuggestionRangeSpan() {
         // 0 is a fully transparent black. Has to be set using #setBackgroundColor
         mBackgroundColor = 0;
     }
 
-    @UnsupportedAppUsage
-    public SuggestionRangeSpan(Parcel src) {
+    /** @hide */
+    public SuggestionRangeSpan(@NonNull Parcel src) {
         mBackgroundColor = src.readInt();
     }
 
+    public static final @NonNull Parcelable.Creator<SuggestionRangeSpan>
+            CREATOR = new Parcelable.Creator<SuggestionRangeSpan>() {
+                @Override
+                public SuggestionRangeSpan createFromParcel(Parcel source) {
+                    return new SuggestionRangeSpan(source);
+                }
+
+                @Override
+                public SuggestionRangeSpan[] newArray(int size) {
+                    return new SuggestionRangeSpan[size];
+                }
+            };
+
     @Override
     public int describeContents() {
         return 0;
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         writeToParcelInternal(dest, flags);
     }
 
@@ -67,13 +78,16 @@
         return TextUtils.SUGGESTION_RANGE_SPAN;
     }
 
-    @UnsupportedAppUsage
     public void setBackgroundColor(int backgroundColor) {
         mBackgroundColor = backgroundColor;
     }
 
+    public int getBackgroundColor() {
+        return mBackgroundColor;
+    }
+
     @Override
-    public void updateDrawState(TextPaint tp) {
+    public void updateDrawState(@NonNull TextPaint tp) {
         tp.bgColor = mBackgroundColor;
     }
 }
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index 2355769..adb379a 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -249,7 +249,7 @@
         mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src);
 
         mTextFontWeight = src.readInt();
-        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader());
+        mTextLocales = src.readParcelable(LocaleList.class.getClassLoader(), android.os.LocaleList.class);
 
         mShadowRadius = src.readFloat();
         mShadowDx = src.readFloat();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7e2792c..8124510 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -58,6 +58,10 @@
      */
     public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
 
+    /** @hide */
+    public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
+            "settings_enable_monitor_phantom_procs";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -81,6 +85,7 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true");
         DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
         DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -89,6 +94,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
         PERSISTENT_FLAGS.add(SETTINGS_PROVIDER_MODEL);
         PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
+        PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
     }
 
     /**
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index b77265b..7b28b8a 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -34,7 +34,7 @@
     private int[] mValues;
     private int mSize;
 
-    private  IntArray(int[] array, int size) {
+    private IntArray(int[] array, int size) {
         mValues = array;
         mSize = Preconditions.checkArgumentInRange(size, 0, array.length, "size");
     }
@@ -178,10 +178,8 @@
     }
 
     @Override
-    public IntArray clone() throws CloneNotSupportedException {
-        final IntArray clone = (IntArray) super.clone();
-        clone.mValues = mValues.clone();
-        return clone;
+    public IntArray clone() {
+        return new IntArray(mValues.clone(), mSize);
     }
 
     /**
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 8c4dcb3..cd077e1 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -22,6 +22,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.util.ArrayDeque;
@@ -63,7 +64,8 @@
         if (mUseLocalTimestamps) {
             logLine = LocalDateTime.now() + " - " + msg;
         } else {
-            logLine = SystemClock.elapsedRealtime() + " / " + Instant.now() + " - " + msg;
+            logLine = Duration.ofMillis(SystemClock.elapsedRealtime())
+                    + " / " + Instant.now() + " - " + msg;
         }
         append(logLine);
     }
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index 9073d3a..6da38c2 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -80,7 +80,7 @@
 
     private MemoryIntArray(Parcel parcel) throws IOException {
         mIsOwner = false;
-        ParcelFileDescriptor pfd = parcel.readParcelable(null);
+        ParcelFileDescriptor pfd = parcel.readParcelable(null, android.os.ParcelFileDescriptor.class);
         if (pfd == null) {
             throw new IOException("No backing file descriptor");
         }
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index b8614cc..678c80a 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -449,8 +449,8 @@
         type = source.readInt();
         displayId = source.readInt();
         displayGroupId = source.readInt();
-        address = source.readParcelable(null);
-        deviceProductInfo = source.readParcelable(null);
+        address = source.readParcelable(null, android.view.DisplayAddress.class);
+        deviceProductInfo = source.readParcelable(null, android.hardware.display.DeviceProductInfo.class);
         name = source.readString8();
         appWidth = source.readInt();
         appHeight = source.readInt();
@@ -475,7 +475,7 @@
         for (int i = 0; i < nColorModes; i++) {
             supportedColorModes[i] = source.readInt();
         }
-        hdrCapabilities = source.readParcelable(null);
+        hdrCapabilities = source.readParcelable(null, android.view.Display.HdrCapabilities.class);
         minimalPostProcessingSupported = source.readBoolean();
         logicalDensityDpi = source.readInt();
         physicalXDpi = source.readFloat();
diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java
index 2660e74..118b03c 100644
--- a/core/java/android/view/KeyboardShortcutInfo.java
+++ b/core/java/android/view/KeyboardShortcutInfo.java
@@ -91,7 +91,7 @@
 
     private KeyboardShortcutInfo(Parcel source) {
         mLabel = source.readCharSequence();
-        mIcon = source.readParcelable(null);
+        mIcon = source.readParcelable(null, android.graphics.drawable.Icon.class);
         mBaseCharacter = (char) source.readInt();
         mKeycode = source.readInt();
         mModifiers = source.readInt();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index b8e50fc..8db62f6 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1495,6 +1495,15 @@
      */
     public static final int TOOL_TYPE_ERASER = 4;
 
+    /**
+     * Tool type constant: The tool is a palm and should be rejected.
+     *
+     * @see #getToolType
+     *
+     * @hide
+     */
+    public static final int TOOL_TYPE_PALM = 5;
+
     // NOTE: If you add a new tool type here you must also add it to:
     //  native/include/android/input.h
 
@@ -1863,7 +1872,7 @@
             float x, float y, float pressure, float size, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
         return obtain(downTime, eventTime, action, x, y, pressure, size, metaState,
-                xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_UNKNOWN,
+                xPrecision, yPrecision, deviceId, edgeFlags, InputDevice.SOURCE_CLASS_POINTER,
                 DEFAULT_DISPLAY);
     }
 
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index 046232a..2dac81c 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -129,7 +129,11 @@
      * The index of the element in the tree in prefix order. This should be used for z-layering
      * to preserve original z-layer order in the hierarchy tree assuming no "boosting" needs to
      * happen.
+     * @deprecated WindowManager may set a z-order different from the prefix order, and has set the
+     *             correct layer for the animation leash already, so this should not be used for
+     *             layer any more.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public final int prefixOrderIndex;
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index af7d86c..3b52709 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -120,6 +120,8 @@
             long relativeToObject, int zorder);
     private static native void nativeSetPosition(long transactionObj, long nativeObject,
             float x, float y);
+    private static native void nativeSetScale(long transactionObj, long nativeObject,
+            float x, float y);
     private static native void nativeSetSize(long transactionObj, long nativeObject, int w, int h);
     private static native void nativeSetTransparentRegionHint(long transactionObj,
             long nativeObject, Region region);
@@ -202,9 +204,13 @@
     private static native void nativeReparent(long transactionObj, long nativeObject,
             long newParentNativeObject);
     private static native void nativeSetBuffer(long transactionObj, long nativeObject,
-            GraphicBuffer buffer);
+            HardwareBuffer buffer);
+    private static native void nativeSetBufferTransform(long transactionObj, long nativeObject,
+            int transform);
     private static native void nativeSetColorSpace(long transactionObj, long nativeObject,
             int colorSpace);
+    private static native void nativeSetDamageRegion(long transactionObj, long nativeObject,
+            Region region);
 
     private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
 
@@ -1333,7 +1339,6 @@
          * Set the initial visibility for the SurfaceControl.
          *
          * @param hidden Whether the Surface is initially HIDDEN.
-         * @hide
          */
         @NonNull
         public Builder setHidden(boolean hidden) {
@@ -2840,16 +2845,38 @@
         }
 
         /**
-         * @hide
+         * Sets the SurfaceControl to the specified position relative to the parent
+         * SurfaceControl
+         *
+         * @param sc The SurfaceControl to change position
+         * @param x the X position
+         * @param y the Y position
+         * @return this transaction
          */
-        @UnsupportedAppUsage
-        public Transaction setPosition(SurfaceControl sc, float x, float y) {
+        @NonNull
+        public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
             checkPreconditions(sc);
             nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
             return this;
         }
 
         /**
+         * Sets the SurfaceControl to the specified scale with (0, 0) as the center point
+         * of the scale.
+         *
+         * @param sc The SurfaceControl to change scale
+         * @param scaleX the X scale
+         * @param scaleY the Y scale
+         * @return this transaction
+         */
+        @NonNull
+        public Transaction setScale(@NonNull SurfaceControl sc, float scaleX, float scaleY) {
+            checkPreconditions(sc);
+            nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
+            return this;
+        }
+
+        /**
          * Set the default buffer size for the SurfaceControl, if there is a
          * {@link Surface} associated with the control, then
          * this will be the default size for buffers dequeued from it.
@@ -3056,7 +3083,9 @@
          * @param sc   SurfaceControl to set crop of.
          * @param crop Bounds of the crop to apply.
          * @hide
+         * @deprecated Use {@link #setCrop(SurfaceControl, Rect)} instead.
          */
+        @Deprecated
         @UnsupportedAppUsage
         public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
             checkPreconditions(sc);
@@ -3071,6 +3100,28 @@
         }
 
         /**
+         * Bounds the surface and its children to the bounds specified. Size of the surface will be
+         * ignored and only the crop and buffer size will be used to determine the bounds of the
+         * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+         * only constrained by the size of its parent bounds.
+         *
+         * @param sc   SurfaceControl to set crop of.
+         * @param crop Bounds of the crop to apply.
+         * @return this This transaction for chaining
+         */
+        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
+            checkPreconditions(sc);
+            if (crop != null) {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
+                        crop.left, crop.top, crop.right, crop.bottom);
+            } else {
+                nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, 0, 0);
+            }
+
+            return this;
+        }
+
+        /**
          * Same as {@link Transaction#setWindowCrop(SurfaceControl, Rect)} but sets the crop rect
          * top left at 0, 0.
          *
@@ -3215,11 +3266,34 @@
         }
 
         /**
-         * Sets the opacity of the surface.  Setting the flag is equivalent to creating the
-         * Surface with the {@link #OPAQUE} flag.
-         * @hide
+         * Indicates whether the surface must be considered opaque, even if its pixel format is
+         * set to translucent. This can be useful if an application needs full RGBA 8888 support
+         * for instance but will still draw every pixel opaque.
+         * <p>
+         * This flag only determines whether opacity will be sampled from the alpha channel.
+         * Plane-alpha from calls to setAlpha() can still result in blended composition
+         * regardless of the opaque setting.
+         *
+         * Combined effects are (assuming a buffer format with an alpha channel):
+         * <ul>
+         * <li>OPAQUE + alpha(1.0) == opaque composition
+         * <li>OPAQUE + alpha(0.x) == blended composition
+         * <li>OPAQUE + alpha(0.0) == no composition
+         * <li>!OPAQUE + alpha(1.0) == blended composition
+         * <li>!OPAQUE + alpha(0.x) == blended composition
+         * <li>!OPAQUE + alpha(0.0) == no composition
+         * </ul>
+         * If the underlying buffer lacks an alpha channel, it is as if setOpaque(true)
+         * were set automatically.
+         *
+         * @see Builder#setOpaque(boolean)
+         *
+         * @param sc The SurfaceControl to update
+         * @param isOpaque true if the buffer's alpha should be ignored, false otherwise
+         * @return this
          */
-        public Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+        @NonNull
+        public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
             checkPreconditions(sc);
             if (isOpaque) {
                 nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
@@ -3498,14 +3572,57 @@
          * created as type {@link #FX_SURFACE_BLAST}
          *
          * @hide
+         * @deprecated Use {@link #setBuffer(SurfaceControl, HardwareBuffer)} instead
          */
+        @Deprecated
         public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+            return setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer));
+        }
+
+        /**
+         * Updates the HardwareBuffer displayed for the SurfaceControl.
+         *
+         * Note that the buffer must be allocated with {@link HardwareBuffer#USAGE_COMPOSER_OVERLAY}
+         * as well as {@link HardwareBuffer#USAGE_GPU_SAMPLED_IMAGE} as the surface control might
+         * be composited using either an overlay or using the GPU.
+         *
+         * @param sc The SurfaceControl to update
+         * @param buffer The buffer to be displayed
+         * @return this
+         */
+        public @NonNull Transaction setBuffer(@NonNull SurfaceControl sc,
+                @Nullable HardwareBuffer buffer) {
             checkPreconditions(sc);
             nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer);
             return this;
         }
 
         /**
+         * Sets the buffer transform that should be applied to the current buffer.
+         *
+         * @param sc The SurfaceControl to update
+         * @param transform The transform to apply to the buffer.
+         * @return this
+         */
+        public @NonNull Transaction setBufferTransform(@NonNull SurfaceControl sc,
+                /* TODO: Mark the intdef */ int transform) {
+            checkPreconditions(sc);
+            nativeSetBufferTransform(mNativeObject, sc.mNativeObject, transform);
+            return this;
+        }
+
+        /**
+         * Updates the region for the content on this surface updated in this transaction.
+         *
+         * If unspecified, the complete surface is assumed to be damaged.
+         */
+        public @NonNull Transaction setDamageRegion(@NonNull SurfaceControl sc,
+                @Nullable Region region) {
+            nativeSetDamageRegion(mNativeObject, sc.mNativeObject, region);
+            return this;
+        }
+
+        /**
          * Set the color space for the SurfaceControl. The supported color spaces are SRGB
          * and Display P3, other color spaces will be treated as SRGB. This can only be used for
          * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ba436e1..3c0597c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -948,9 +948,10 @@
     // When the listener is updated, we will get at least a single position update call so we can
     // guarantee any changes we post will be applied.
     private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
-            @Nullable Transaction geometryTransaction) {
+            Transaction geometryTransaction) {
         if (mPositionListener != null) {
             mRenderNode.removePositionUpdateListener(mPositionListener);
+            geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
         }
         mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
                 geometryTransaction);
@@ -958,7 +959,8 @@
     }
 
     private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
-            boolean creating, boolean sizeChanged, boolean hintChanged) {
+            boolean creating, boolean sizeChanged, boolean hintChanged,
+            Transaction geometryTransaction) {
         boolean realSizeChanged = false;
 
         mSurfaceLock.lock();
@@ -996,10 +998,6 @@
                 mSurfaceAlpha = alpha;
             }
 
-            // While creating the surface, we will set it's initial
-            // geometry. Outside of that though, we should generally
-            // leave it to the RenderThread.
-            Transaction geometryTransaction = new Transaction();
             geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
             if ((sizeChanged || hintChanged) && !creating) {
                 setBufferSize(geometryTransaction);
@@ -1022,20 +1020,18 @@
                             mSurfaceHeight);
                 }
 
-                boolean applyChangesOnRenderThread =
-                        sizeChanged && !creating && isHardwareAccelerated();
                 if (isHardwareAccelerated()) {
                     // This will consume the passed in transaction and the transaction will be
                     // applied on a render worker thread.
                     replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
-                            applyChangesOnRenderThread ? geometryTransaction : null);
+                            geometryTransaction);
                 }
                 if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
-                            "%d updateSurfacePosition %s"
+                            "%d performSurfaceTransaction %s "
                                 + "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
                             System.identityHashCode(this),
-                            applyChangesOnRenderThread ? "RenderWorker" : "UiThread",
+                            isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
                             mScreenRect.left, mScreenRect.top, mScreenRect.right,
                             mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
                 }
@@ -1147,12 +1143,14 @@
 
                 final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
                 mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
+                // Collect all geometry changes and apply these changes on the RenderThread worker
+                // via the RenderNode.PositionUpdateListener.
+                final Transaction geometryTransaction = new Transaction();
                 if (creating) {
                     updateOpaqueFlag();
                     final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
                     if (mUseBlastAdapter) {
-                        createBlastSurfaceControls(viewRoot, name);
+                        createBlastSurfaceControls(viewRoot, name, geometryTransaction);
                     } else {
                         mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
                     }
@@ -1161,7 +1159,7 @@
                 }
 
                 final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
-                        translator, creating, sizeChanged, hintChanged);
+                        translator, creating, sizeChanged, hintChanged, geometryTransaction);
                 final boolean redrawNeeded = sizeChanged || creating || hintChanged
                         || (mVisible && !mDrawFinished);
 
@@ -1335,7 +1333,8 @@
     // is still alive, the old buffers will continue to be presented until replaced by buffers from
     // the new adapter. This means we do not need to track the old surface control and destroy it
     // after the client has drawn to avoid any flickers.
-    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+    private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
+            Transaction geometryTransaction) {
         if (mSurfaceControl == null) {
             mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
                     .setName(name)
@@ -1376,8 +1375,9 @@
         }
         mTransformHint = viewRoot.getBufferTransformHint();
         mBlastSurfaceControl.setTransformHint(mTransformHint);
-        mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
-                mSurfaceHeight, mFormat);
+        mBlastBufferQueue = new BLASTBufferQueue(name);
+        mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat,
+                geometryTransaction);
     }
 
     private void onDrawFinished(Transaction t) {
@@ -1558,6 +1558,10 @@
                 applyOrMergeTransaction(mRtTransaction, frameNumber);
             }
         }
+
+        public Transaction getTransaction() {
+            return mPositionChangedTransaction;
+        }
     }
 
     private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1651,6 +1655,11 @@
         @Override
         public void setFixedSize(int width, int height) {
             if (mRequestedWidth != width || mRequestedHeight != height) {
+                if (DEBUG_POSITION) {
+                    Log.d(TAG, String.format("%d setFixedSize %dx%d -> %dx%d",
+                            System.identityHashCode(this), mRequestedWidth, mRequestedHeight, width,
+                                    height));
+                }
                 mRequestedWidth = width;
                 mRequestedHeight = height;
                 requestLayout();
@@ -1660,6 +1669,10 @@
         @Override
         public void setSizeFromLayout() {
             if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+                if (DEBUG_POSITION) {
+                    Log.d(TAG, String.format("%d setSizeFromLayout was %dx%d",
+                            System.identityHashCode(this), mRequestedWidth, mRequestedHeight));
+                }
                 mRequestedWidth = mRequestedHeight = -1;
                 requestLayout();
             }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f529172..8fda48b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26977,44 +26977,38 @@
      * Handles drag events sent by the system following a call to
      * {@link android.view.View#startDragAndDrop(ClipData,DragShadowBuilder,Object,int)
      * startDragAndDrop()}.
-     *<p>
-     * When the system calls this method, it passes a
-     * {@link android.view.DragEvent} object. A call to
-     * {@link android.view.DragEvent#getAction()} returns one of the action type constants defined
-     * in DragEvent. The method uses these to determine what is happening in the drag and drop
-     * operation.
-     * </p>
      * <p>
-     * The default implementation returns false, except if an {@link OnReceiveContentListener}
-     * is {@link #setOnReceiveContentListener set} for this view. If an
-     * {@link OnReceiveContentListener} is set, the default implementation...
+     * The system calls this method and passes a {@link DragEvent} object in response to drag and
+     * drop events. This method can then call {@link DragEvent#getAction()} to determine the state
+     * of the drag and drop operation.
+     * <p>
+     * The default implementation returns {@code false} unless an {@link OnReceiveContentListener}
+     * has been set for this view (see {@link #setOnReceiveContentListener}), in which case
+     * the default implementation does the following:
      * <ul>
-     * <li>returns true for an
-     * {@link android.view.DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event
-     * <li>calls {@link #performReceiveContent} for an
-     * {@link android.view.DragEvent#ACTION_DROP ACTION_DROP} event
-     * <li>returns true for an {@link android.view.DragEvent#ACTION_DROP ACTION_DROP} event, if
-     * the listener consumed some or all of the content
+     *   <li>Returns {@code true} for an
+     *     {@link DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} event
+     *   <li>Calls {@link #performReceiveContent} for an
+     *     {@link DragEvent#ACTION_DROP ACTION_DROP} event
+     *   <li>Returns {@code true} for an {@link DragEvent#ACTION_DROP ACTION_DROP} event if the
+     *     {@code OnReceiveContentListener} consumed some or all of the content
      * </ul>
-     * </p>
      *
-     * @param event The {@link android.view.DragEvent} sent by the system.
-     * The {@link android.view.DragEvent#getAction()} method returns an action type constant defined
-     * in DragEvent, indicating the type of drag event represented by this object.
-     * @return {@code true} if the method was successful, otherwise {@code false}.
-     * <p>
-     *  The method should return {@code true} in response to an action type of
-     *  {@link android.view.DragEvent#ACTION_DRAG_STARTED} to receive drag events for the current
-     *  operation.
-     * </p>
-     * <p>
-     *  The method should also return {@code true} in response to an action type of
-     *  {@link android.view.DragEvent#ACTION_DROP} if it consumed the drop, or
-     *  {@code false} if it didn't.
-     * </p>
-     * <p>
-     *  For all other events, the return value is ignored.
-     * </p>
+     * @param event The {@link DragEvent} object sent by the system. The
+     *   {@link DragEvent#getAction()} method returns an action type constant that indicates the
+     *   type of drag event represented by this object.
+     * @return {@code true} if the method successfully handled the drag event, otherwise
+     *   {@code false}.
+     *   <p>
+     *     The method must return {@code true} in response to an
+     *     {@link DragEvent#ACTION_DRAG_STARTED ACTION_DRAG_STARTED} action type to continue to
+     *     receive drag events for the current drag and drop operation.
+     *   <p>
+     *     The method should return {@code true} in response to an
+     *     {@link DragEvent#ACTION_DROP ACTION_DROP} action type if the dropped data was consumed
+     *     (at least partially); {@code false}, if none of the data was consumed.
+     *   <p>
+     *     For all other events, the return value is {@code false}.
      */
     public boolean onDragEvent(DragEvent event) {
         if (mListenerInfo == null || mListenerInfo.mOnReceiveContentListener == null) {
@@ -29003,27 +28997,27 @@
     }
 
     /**
-     * Interface definition for a callback to be invoked when a drag is being dispatched
-     * to this view.  The callback will be invoked before the hosting view's own
-     * onDrag(event) method.  If the listener wants to fall back to the hosting view's
-     * onDrag(event) behavior, it should return 'false' from this callback.
+     * Interface definition for a listener that's invoked when a drag event is dispatched to this
+     * view. The listener is invoked before the view's own
+     * {@link #onDragEvent(DragEvent)} method. To fall back to the view's
+     * {@code onDragEvent(DragEvent)} behavior, return {@code false} from the listener method.
      *
      * <div class="special reference">
-     * <h3>Developer Guides</h3>
-     * <p>For a guide to implementing drag and drop features, read the
-     * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
+     *   <h3>Developer Guides</h3>
+     *   <p>For a guide to implementing drag and drop features, see the
+     *   <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and drop</a> developer guide.</p>
      * </div>
      */
     public interface OnDragListener {
         /**
-         * Called when a drag event is dispatched to a view. This allows listeners
-         * to get a chance to override base View behavior.
+         * Called when a drag event is dispatched to a view. Enables listeners to override the
+         * base behavior provided by {@link #onDragEvent(DragEvent)}.
          *
-         * @param v The View that received the drag event.
-         * @param event The {@link android.view.DragEvent} object for the drag event.
-         * @return {@code true} if the drag event was handled successfully, or {@code false}
-         * if the drag event was not handled. Note that {@code false} will trigger the View
-         * to call its {@link #onDragEvent(DragEvent) onDragEvent()} handler.
+         * @param v The {@code View} that received the drag event.
+         * @param event The event object for the drag event.
+         * @return {@code true} if the drag event was handled successfully; {@code false}, if the
+         *   drag event was not handled. <b>Note:</b> A {@code false} return value triggers the
+         *   view's {@link #onDragEvent(DragEvent)} handler.
          */
         boolean onDrag(View v, DragEvent event);
     }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 07d5fc5..25e0eca 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1896,7 +1896,7 @@
 
         private Canvas mCanvas;
         private Bitmap mBitmap;
-        private boolean mEnabledHwBitmapsInSwMode;
+        private boolean mEnabledHwFeaturesInSwMode;
 
         @Override
         public Canvas getCanvas(View view, int width, int height) {
@@ -1913,7 +1913,7 @@
             if (mCanvas == null) {
                 mCanvas = new Canvas();
             }
-            mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled();
+            mEnabledHwFeaturesInSwMode = mCanvas.isHwFeaturesInSwModeEnabled();
             mCanvas.setBitmap(mBitmap);
             return mCanvas;
         }
@@ -1921,7 +1921,7 @@
         @Override
         public Bitmap createBitmap() {
             mCanvas.setBitmap(null);
-            mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode);
+            mCanvas.setHwFeaturesInSwModeEnabled(mEnabledHwFeaturesInSwMode);
             return mBitmap;
         }
     }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1e2895d..70505fc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -533,10 +533,11 @@
 
     boolean mReportNextDraw;
     /**
-     * Set if the reportDraw was requested from WM. If just a local report draw was invoked, there's
-     * no need to report back to system server and can just apply immediately on the client.
+     * Set whether the draw should use blast sync. This is in case the draw is canceled,
+     * but will be rescheduled. We still want the next draw to be sync.
      */
-    boolean mReportDrawToWm;
+    boolean mNextDrawUseBlastSync;
+
     boolean mFullRedrawNeeded;
     boolean mNewSurfaceNeeded;
     boolean mForceNextWindowRelayout;
@@ -2761,7 +2762,7 @@
             }
         }
         final boolean wasReportNextDraw = mReportNextDraw;
-        boolean useBlastSync = false;
+        boolean useBlastSync = mNextDrawUseBlastSync;
 
         if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
                 || mForceNextWindowRelayout) {
@@ -3292,9 +3293,11 @@
                 mPendingTransitions.clear();
             }
             performDraw(useBlastSync);
+            mNextDrawUseBlastSync = false;
         } else {
             if (isViewVisible) {
                 // Try again
+                mNextDrawUseBlastSync = useBlastSync;
                 scheduleTraversals();
             } else {
                 if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -3984,17 +3987,8 @@
         }
         mDrawsNeededToReport = 0;
 
-        if (!mReportDrawToWm) {
-            if (DEBUG_BLAST) {
-                Log.d(mTag, "No need to report finishDrawing. Apply immediately");
-            }
-            mSurfaceChangedTransaction.apply();
-            return;
-        }
-
         try {
             mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
-            mReportDrawToWm = false;
         } catch (RemoteException e) {
             Log.e(mTag, "Unable to report draw finished", e);
             mSurfaceChangedTransaction.apply();
@@ -4916,13 +4910,14 @@
         }
     }
 
-    private void updateColorModeIfNeeded(int colorMode) {
+    private void updateColorModeIfNeeded(@ActivityInfo.ColorMode int colorMode) {
         if (mAttachInfo.mThreadedRenderer == null) {
             return;
         }
         // TODO: Centralize this sanitization? Why do we let setting bad modes?
         // Alternatively, can we just let HWUI figure it out? Do we need to care here?
-        if (!getConfiguration().isScreenWideColorGamut()) {
+        if (colorMode != ActivityInfo.COLOR_MODE_A8
+                && !getConfiguration().isScreenWideColorGamut()) {
             colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
         }
         mAttachInfo.mThreadedRenderer.setColorMode(colorMode);
@@ -8497,13 +8492,13 @@
             MotionEvent me = (MotionEvent) event;
             if (me.getAction() == MotionEvent.ACTION_CANCEL) {
                 EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Motion - Cancel",
-                        getTitle());
+                        getTitle().toString());
             }
         } else if (event instanceof KeyEvent) {
             KeyEvent ke = (KeyEvent) event;
             if (ke.isCanceled()) {
                 EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Key - Cancel",
-                        getTitle());
+                        getTitle().toString());
             }
         }
         // Always enqueue the input event in order, regardless of its time stamp.
@@ -9604,7 +9599,6 @@
         if (mReportNextDraw == false) {
             drawPending();
         }
-        mReportDrawToWm = true;
         mReportNextDraw = true;
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 6bfb14b..83712b4 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -1332,7 +1332,7 @@
         record.mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         record.mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
         record.mParcelableData = parcel.readParcelable(null);
-        parcel.readList(record.mText, null);
+        parcel.readList(record.mText, null, java.lang.CharSequence.class);
         record.mSourceWindowId = parcel.readInt();
         record.mSourceNodeId = parcel.readLong();
         record.mSourceDisplayId = parcel.readInt();
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index dc61727..7a33507 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -273,6 +273,9 @@
     private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
             mServicesStateChangeListeners = new ArrayMap<>();
 
+    private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
+            mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
+
     /**
      * Map from a view's accessibility id to the list of request preparers set for that view
      */
@@ -353,6 +356,21 @@
     }
 
     /**
+     * Listener for the audio description by default state. To listen for
+     * changes to the audio description by default state on the device,
+     * implement this interface and register it with the system by calling
+     * {@link #addAudioDescriptionRequestedChangeListener}.
+     */
+    public interface AudioDescriptionRequestedChangeListener {
+        /**
+         * Called when the audio description enabled state changes.
+         *
+         * @param enabled Whether audio description by default is enabled.
+         */
+        void onAudioDescriptionRequestedChanged(boolean enabled);
+    }
+
+    /**
      * Policy to inject behavior into the accessibility manager.
      *
      * @hide
@@ -1159,6 +1177,35 @@
     }
 
     /**
+     * Registers a {@link AudioDescriptionRequestedChangeListener}
+     * for changes in the audio description by default state of the system.
+     * The value could be read via {@link #isAudioDescriptionRequested}.
+     *
+     * @param executor The executor on which the listener should be called back.
+     * @param listener The listener.
+     */
+    public void addAudioDescriptionRequestedChangeListener(
+            @NonNull Executor executor,
+            @NonNull AudioDescriptionRequestedChangeListener listener) {
+        synchronized (mLock) {
+            mAudioDescriptionRequestedChangeListeners.put(listener, executor);
+        }
+    }
+
+    /**
+     * Unregisters a {@link AudioDescriptionRequestedChangeListener}.
+     *
+     * @param listener The listener.
+     * @return True if listener was previously registered.
+     */
+    public boolean removeAudioDescriptionRequestedChangeListener(
+            @NonNull AudioDescriptionRequestedChangeListener listener) {
+        synchronized (mLock) {
+            return (mAudioDescriptionRequestedChangeListeners.remove(listener) != null);
+        }
+    }
+
+    /**
      * Sets the {@link AccessibilityPolicy} controlling this manager.
      *
      * @param policy The policy.
@@ -1303,7 +1350,7 @@
         final boolean wasEnabled = isEnabled();
         final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
         final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
-
+        final boolean wasAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
 
         // Ensure listeners get current state from isZzzEnabled() calls.
         mIsEnabled = enabled;
@@ -1323,6 +1370,11 @@
             notifyHighTextContrastStateChanged();
         }
 
+        if (wasAudioDescriptionByDefaultRequested
+                != audioDescriptionEnabled) {
+            notifyAudioDescriptionbyDefaultStateChanged();
+        }
+
         updateAccessibilityTracingState(stateFlags);
     }
 
@@ -1688,15 +1740,20 @@
 
     /**
      * Determines if users want to select sound track with audio description by default.
-     *
+     * <p>
      * Audio description, also referred to as a video description, described video, or
      * more precisely called a visual description, is a form of narration used to provide
      * information surrounding key visual elements in a media work for the benefit of
      * blind and visually impaired consumers.
-     *
+     * </p>
+     * <p>
      * The method provides the preference value to content provider apps to select the
      * default sound track during playing a video or movie.
-     *
+     * </p>
+     * <p>
+     * Add listener to detect the state change via
+     * {@link #addAudioDescriptionRequestedChangeListener}
+     * </p>
      * @return {@code true} if the audio description is enabled, {@code false} otherwise.
      */
     public boolean isAudioDescriptionRequested() {
@@ -1804,6 +1861,29 @@
     }
 
     /**
+     * Notifies the registered {@link AudioDescriptionStateChangeListener}s.
+     */
+    private void notifyAudioDescriptionbyDefaultStateChanged() {
+        final boolean isAudioDescriptionByDefaultRequested;
+        final ArrayMap<AudioDescriptionRequestedChangeListener, Executor> listeners;
+        synchronized (mLock) {
+            if (mAudioDescriptionRequestedChangeListeners.isEmpty()) {
+                return;
+            }
+            isAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
+            listeners = new ArrayMap<>(mAudioDescriptionRequestedChangeListeners);
+        }
+
+        final int numListeners = listeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            final AudioDescriptionRequestedChangeListener listener = listeners.keyAt(i);
+            listeners.valueAt(i).execute(() ->
+                    listener.onAudioDescriptionRequestedChanged(
+                        isAudioDescriptionByDefaultRequested));
+        }
+    }
+
+    /**
      * Update mAccessibilityTracingState.
      */
     private void updateAccessibilityTracingState(int stateFlag) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 7680aa6..9511958 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -4101,10 +4101,12 @@
                 : new CollectionInfo(ci.mRowCount, ci.mColumnCount,
                                      ci.mHierarchical, ci.mSelectionMode);
         CollectionItemInfo cii = other.mCollectionItemInfo;
+        CollectionItemInfo.Builder builder = new CollectionItemInfo.Builder();
         mCollectionItemInfo = (cii == null)  ? null
-                : new CollectionItemInfo(cii.mRowTitle, cii.mRowIndex, cii.mRowSpan,
-                        cii.mColumnTitle, cii.mColumnIndex, cii.mColumnSpan,
-                        cii.mHeading, cii.mSelected);
+                : builder.setRowTitle(cii.mRowTitle).setRowIndex(cii.mRowIndex).setRowSpan(
+                        cii.mRowSpan).setColumnTitle(cii.mColumnTitle).setColumnIndex(
+                                cii.mColumnIndex).setColumnSpan(cii.mColumnSpan).setHeading(
+                                        cii.mHeading).setSelected(cii.mSelected).build();
         ExtraRenderingInfo ti = other.mExtraRenderingInfo;
         mExtraRenderingInfo = (ti == null) ? null
                 : new ExtraRenderingInfo(ti);
@@ -5104,6 +5106,12 @@
         @NonNull public static final AccessibilityAction ACTION_SWIPE_DOWN =
             new AccessibilityAction(R.id.accessibilityActionSwipeDown);
 
+        /**
+         * Action to show suggestions for editable text.
+         */
+        @NonNull public static final AccessibilityAction ACTION_SHOW_SUGGESTIONS =
+                new AccessibilityAction(R.id.accessibilityActionShowSuggestions);
+
         private final int mActionId;
         private final CharSequence mLabel;
 
@@ -5670,6 +5678,10 @@
         private String mRowTitle;
         private String mColumnTitle;
 
+        private CollectionItemInfo() {
+            /* do nothing */
+        }
+
         /**
          * Creates a new instance.
          *
@@ -5711,6 +5723,7 @@
          * @param columnSpan The number of columns the item spans.
          * @param heading Whether the item is a heading.
          * @param selected Whether the item is selected.
+         * @hide
          */
         public CollectionItemInfo(@Nullable String rowTitle, int rowIndex, int rowSpan,
                 @Nullable String columnTitle, int columnIndex, int columnSpan, boolean heading,
@@ -5821,6 +5834,140 @@
             mRowTitle = null;
             mColumnTitle = null;
         }
+
+        /**
+         * Builder for creating {@link CollectionItemInfo} objects.
+         */
+        public static final class Builder {
+            private boolean mHeading;
+            private int mColumnIndex;
+            private int mRowIndex;
+            private int mColumnSpan;
+            private int mRowSpan;
+            private boolean mSelected;
+            private String mRowTitle;
+            private String mColumnTitle;
+
+            /**
+             * Creates a new Builder.
+             */
+            public Builder() {
+            }
+
+            /**
+             * Sets the collection item is a heading.
+             *
+             * @param heading The heading state
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setHeading(boolean heading) {
+                mHeading = heading;
+                return this;
+            }
+
+            /**
+             * Sets the column index at which the item is located.
+             *
+             * @param columnIndex The column index
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setColumnIndex(int columnIndex) {
+                mColumnIndex = columnIndex;
+                return this;
+            }
+
+            /**
+             * Sets the row index at which the item is located.
+             *
+             * @param rowIndex The row index
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setRowIndex(int rowIndex) {
+                mRowIndex = rowIndex;
+                return this;
+            }
+
+            /**
+             * Sets the number of columns the item spans.
+             *
+             * @param columnSpan The number of columns spans
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setColumnSpan(int columnSpan) {
+                mColumnSpan = columnSpan;
+                return this;
+            }
+
+            /**
+             * Sets the number of rows the item spans.
+             *
+             * @param rowSpan The number of rows spans
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setRowSpan(int rowSpan) {
+                mRowSpan = rowSpan;
+                return this;
+            }
+
+            /**
+             * Sets the collection item is selected.
+             *
+             * @param selected The number of rows spans
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setSelected(boolean selected) {
+                mSelected = selected;
+                return this;
+            }
+
+            /**
+             * Sets the row title at which the item is located.
+             *
+             * @param rowTitle The row title
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setRowTitle(@Nullable String rowTitle) {
+                mRowTitle = rowTitle;
+                return this;
+            }
+
+            /**
+             * Sets the column title at which the item is located.
+             *
+             * @param columnTitle The column title
+             * @return This builder
+             */
+            @NonNull
+            public CollectionItemInfo.Builder setColumnTitle(@Nullable String columnTitle) {
+                mColumnTitle = columnTitle;
+                return this;
+            }
+
+            /**
+             * Builds and returns a {@link CollectionItemInfo}.
+             */
+            @NonNull
+            public CollectionItemInfo build() {
+                CollectionItemInfo collectionItemInfo = new CollectionItemInfo();
+                collectionItemInfo.mHeading = mHeading;
+                collectionItemInfo.mColumnIndex = mColumnIndex;
+                collectionItemInfo.mRowIndex = mRowIndex;
+                collectionItemInfo.mColumnSpan = mColumnSpan;
+                collectionItemInfo.mRowSpan = mRowSpan;
+                collectionItemInfo.mSelected = mSelected;
+                collectionItemInfo.mRowTitle = mRowTitle;
+                collectionItemInfo.mColumnTitle = mColumnTitle;
+
+                return collectionItemInfo;
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index 76e2261..36e779a 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -908,7 +908,7 @@
                 final int count = source.readInt();
                 for (int i = 0; i < count; i++) {
                     List<AccessibilityWindowInfo> windows = new ArrayList<>();
-                    source.readParcelableList(windows, loader);
+                    source.readParcelableList(windows, loader, android.view.accessibility.AccessibilityWindowInfo.class);
                     array.put(source.readInt(), windows);
                 }
                 return array;
diff --git a/core/java/android/view/autofill/ParcelableMap.java b/core/java/android/view/autofill/ParcelableMap.java
index d8459aa..3fa7734 100644
--- a/core/java/android/view/autofill/ParcelableMap.java
+++ b/core/java/android/view/autofill/ParcelableMap.java
@@ -56,8 +56,8 @@
                     ParcelableMap map = new ParcelableMap(size);
 
                     for (int i = 0; i < size; i++) {
-                        AutofillId key = source.readParcelable(null);
-                        AutofillValue value = source.readParcelable(null);
+                        AutofillId key = source.readParcelable(null, android.view.autofill.AutofillId.class);
+                        AutofillValue value = source.readParcelable(null, android.view.autofill.AutofillValue.class);
 
                         map.put(key, value);
                     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java
index 027c8d2..685ea1a 100644
--- a/core/java/android/view/contentcapture/ContentCaptureCondition.java
+++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java
@@ -133,7 +133,7 @@
 
                 @Override
                 public ContentCaptureCondition createFromParcel(@NonNull Parcel parcel) {
-                    return new ContentCaptureCondition(parcel.readParcelable(null),
+                    return new ContentCaptureCondition(parcel.readParcelable(null, android.content.LocusId.class),
                             parcel.readInt());
                 }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureContext.java b/core/java/android/view/contentcapture/ContentCaptureContext.java
index 3bc9a96..59b5286 100644
--- a/core/java/android/view/contentcapture/ContentCaptureContext.java
+++ b/core/java/android/view/contentcapture/ContentCaptureContext.java
@@ -419,7 +419,7 @@
             final ContentCaptureContext clientContext;
             if (hasClientContext) {
                 // Must reconstruct the client context using the Builder API
-                final LocusId id = parcel.readParcelable(null);
+                final LocusId id = parcel.readParcelable(null, android.content.LocusId.class);
                 final Bundle extras = parcel.readBundle();
                 final Builder builder = new Builder(id);
                 if (extras != null) builder.setExtras(extras);
@@ -427,7 +427,7 @@
             } else {
                 clientContext = null;
             }
-            final ComponentName componentName = parcel.readParcelable(null);
+            final ComponentName componentName = parcel.readParcelable(null, android.content.ComponentName.class);
             if (componentName == null) {
                 // Client-state only
                 return clientContext;
diff --git a/core/java/android/view/contentcapture/ContentCaptureEvent.java b/core/java/android/view/contentcapture/ContentCaptureEvent.java
index 0f4bc19..ba4176f 100644
--- a/core/java/android/view/contentcapture/ContentCaptureEvent.java
+++ b/core/java/android/view/contentcapture/ContentCaptureEvent.java
@@ -620,7 +620,7 @@
             final int type = parcel.readInt();
             final long eventTime  = parcel.readLong();
             final ContentCaptureEvent event = new ContentCaptureEvent(sessionId, type, eventTime);
-            final AutofillId id = parcel.readParcelable(null);
+            final AutofillId id = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
             if (id != null) {
                 event.setAutofillId(id);
             }
@@ -637,13 +637,13 @@
                 event.setParentSessionId(parcel.readInt());
             }
             if (type == TYPE_SESSION_STARTED || type == TYPE_CONTEXT_UPDATED) {
-                event.setClientContext(parcel.readParcelable(null));
+                event.setClientContext(parcel.readParcelable(null, android.view.contentcapture.ContentCaptureContext.class));
             }
             if (type == TYPE_VIEW_INSETS_CHANGED) {
-                event.setInsets(parcel.readParcelable(null));
+                event.setInsets(parcel.readParcelable(null, android.graphics.Insets.class));
             }
             if (type == TYPE_WINDOW_BOUNDS_CHANGED) {
-                event.setBounds(parcel.readParcelable(null));
+                event.setBounds(parcel.readParcelable(null, android.graphics.Rect.class));
             }
             if (type == TYPE_VIEW_TEXT_CHANGED) {
                 event.setComposingIndex(parcel.readInt(), parcel.readInt());
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index 1b4a00f..1762a58 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -124,10 +124,10 @@
         mFlags = nodeFlags;
 
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_ID) != 0) {
-            mAutofillId = parcel.readParcelable(null);
+            mAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_PARENT_ID) != 0) {
-            mParentAutofillId = parcel.readParcelable(null);
+            mParentAutofillId = parcel.readParcelable(null, android.view.autofill.AutofillId.class);
         }
         if ((nodeFlags & FLAGS_HAS_TEXT) != 0) {
             mText = new ViewNodeText(parcel, (nodeFlags & FLAGS_HAS_COMPLEX_TEXT) == 0);
@@ -169,7 +169,7 @@
             mExtras = parcel.readBundle();
         }
         if ((nodeFlags & FLAGS_HAS_LOCALE_LIST) != 0) {
-            mLocaleList = parcel.readParcelable(null);
+            mLocaleList = parcel.readParcelable(null, android.os.LocaleList.class);
         }
         if ((nodeFlags & FLAGS_HAS_MIME_TYPES) != 0) {
             mReceiveContentMimeTypes = parcel.readStringArray();
@@ -196,7 +196,7 @@
             mAutofillHints = parcel.readStringArray();
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_VALUE) != 0) {
-            mAutofillValue = parcel.readParcelable(null);
+            mAutofillValue = parcel.readParcelable(null, android.view.autofill.AutofillValue.class);
         }
         if ((nodeFlags & FLAGS_HAS_AUTOFILL_OPTIONS) != 0) {
             mAutofillOptions = parcel.readCharSequenceArray();
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index fbc9470..437e54f 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -140,7 +140,7 @@
         mInsertionMarkerTop = source.readFloat();
         mInsertionMarkerBaseline = source.readFloat();
         mInsertionMarkerBottom = source.readFloat();
-        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader());
+        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class);
         mMatrixValues = source.createFloatArray();
     }
 
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 74ca913..09a1448 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -92,6 +92,7 @@
      *                1                  TYPE_TEXT_FLAG_MULTI_LINE
      *               1                   TYPE_TEXT_FLAG_IME_MULTI_LINE
      *              1                    TYPE_TEXT_FLAG_NO_SUGGESTIONS
+     *             1                     TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS
      * |-------|-------|-------|-------|
      *                                1  TYPE_CLASS_NUMBER
      *                             1     TYPE_NUMBER_VARIATION_PASSWORD
@@ -1066,7 +1067,7 @@
                     res.hintText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
                     res.packageName = source.readString();
-                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader());
+                    res.autofillId = source.readParcelable(AutofillId.class.getClassLoader(), android.view.autofill.AutofillId.class);
                     res.fieldId = source.readInt();
                     res.fieldName = source.readString();
                     res.extras = source.readBundle();
@@ -1090,4 +1091,4 @@
     public int describeContents() {
         return 0;
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index e1e1755..70279cc 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -490,7 +490,7 @@
         boolean clientSupported = (flg & 0x200) != 0;
         int maxSuggestionCount = in.readInt();
         List<InlinePresentationSpec> inlinePresentationSpecs = new ArrayList<>();
-        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader());
+        in.readParcelableList(inlinePresentationSpecs, InlinePresentationSpec.class.getClassLoader(), android.widget.inline.InlinePresentationSpec.class);
         String hostPackageName = in.readString();
         LocaleList supportedLocales = (LocaleList) in.readTypedObject(LocaleList.CREATOR);
         Bundle extras = in.readBundle();
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
index b393c67..532fc85 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -170,7 +170,7 @@
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
         List<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader());
+        in.readParcelableList(inlineSuggestions, InlineSuggestion.class.getClassLoader(), android.view.inputmethod.InlineSuggestion.class);
 
         this.mInlineSuggestions = inlineSuggestions;
         com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 3b15db2..b85fe7c 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -836,20 +836,25 @@
     boolean beginBatchEdit();
 
     /**
-     * Tell the editor that you are done with a batch edit previously
-     * initiated with {@link #beginBatchEdit}. This ends the latest
-     * batch only.
+     * Tell the editor that you are done with a batch edit previously initiated with
+     * {@link #beginBatchEdit()}. This ends the latest batch only.
      *
-     * <p><strong>IME authors:</strong> make sure you call this
-     * exactly once for each call to {@link #beginBatchEdit}.</p>
+     * <p><strong>IME authors:</strong> make sure you call this exactly once for each call to
+     * {@link #beginBatchEdit()}.</p>
      *
-     * <p><strong>Editor authors:</strong> please be careful about
-     * batch edit nesting. Updates still to be held back until the end
-     * of the last batch edit.</p>
+     * <p><strong>Editor authors:</strong> please be careful about batch edit nesting. Updates still
+     * to be held back until the end of the last batch edit.  In case you are delegating this API
+     * call to the one obtained from
+     * {@link android.widget.EditText#onCreateInputConnection(EditorInfo)}, there was an off-by-one
+     * that had returned {@code true} when its nested batch edit count becomes {@code 0} as a result
+     * of invoking this API.  This bug is fixed in {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     * </p>
      *
-     * @return true if there is still a batch edit in progress after closing
-     * the latest one (in other words, if the nesting count is > 0), false
-     * otherwise or if the input connection is no longer valid.
+     * @return For editor authors, you must return {@code true} if a batch edit is still in progress
+     *         after closing the latest one (in other words, if the nesting count is still a
+     *         positive number). Return {@code false} otherwise.  For IME authors, you will
+     *         always receive {@code true} as long as the request was sent to the editor, and
+     *         receive {@code false} only if the input connection is no longer valid.
      */
     boolean endBatchEdit();
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 96198c6..7e6e6fd 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -141,6 +141,12 @@
     private final int mHandledConfigChanges;
 
     /**
+     * The flag whether this IME supports Handwriting using stylus input.
+     */
+    private final boolean mSupportsStylusHandwriting;
+
+
+    /**
      * @param service the {@link ResolveInfo} corresponds in which the IME is implemented.
      * @return a unique ID to be returned by {@link #getId()}. We have used
      *         {@link ComponentName#flattenToShortString()} for this purpose (and it is already
@@ -234,6 +240,8 @@
                     com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true);
             mHandledConfigChanges = sa.getInt(
                     com.android.internal.R.styleable.InputMethod_configChanges, 0);
+            mSupportsStylusHandwriting = sa.getBoolean(
+                    com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false);
             sa.recycle();
 
             final int depth = parser.getDepth();
@@ -323,6 +331,7 @@
         mService = ResolveInfo.CREATOR.createFromParcel(source);
         mSubtypes = new InputMethodSubtypeArray(source);
         mHandledConfigChanges = source.readInt();
+        mSupportsStylusHandwriting = source.readBoolean();
         mForceDefault = false;
     }
 
@@ -335,7 +344,7 @@
                 settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
                 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
-                0 /* handledConfigChanges */);
+                0 /* handledConfigChanges */, false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -349,7 +358,8 @@
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
                 settingsActivity, null /* subtypes */, 0 /* isDefaultResId */,
                 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
-                false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges);
+                false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges,
+                false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -361,7 +371,8 @@
             boolean forceDefault) {
         this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
                 true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
-                false /* isVrOnly */, 0 /* handledconfigChanges */);
+                false /* isVrOnly */, 0 /* handledconfigChanges */,
+                false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -373,7 +384,7 @@
             boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) {
         this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault,
                 supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
-                0 /* handledConfigChanges */);
+                0 /* handledConfigChanges */, false /* supportsStylusHandwriting */);
     }
 
     /**
@@ -383,7 +394,7 @@
     public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity,
             List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault,
             boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
-            boolean isVrOnly, int handledConfigChanges) {
+            boolean isVrOnly, int handledConfigChanges, boolean supportsStylusHandwriting) {
         final ServiceInfo si = ri.serviceInfo;
         mService = ri;
         mId = new ComponentName(si.packageName, si.name).flattenToShortString();
@@ -398,6 +409,7 @@
         mShowInInputMethodPicker = true;
         mIsVrOnly = isVrOnly;
         mHandledConfigChanges = handledConfigChanges;
+        mSupportsStylusHandwriting = supportsStylusHandwriting;
     }
 
     private static ResolveInfo buildFakeResolveInfo(String packageName, String className,
@@ -556,6 +568,14 @@
         return mHandledConfigChanges;
     }
 
+    /**
+     * Returns if IME supports handwriting using stylus input.
+     * @attr ref android.R.styleable#InputMethod_supportsStylusHandwriting
+     */
+    public boolean supportsStylusHandwriting() {
+        return mSupportsStylusHandwriting;
+    }
+
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
                 + " mSettingsActivityName=" + mSettingsActivityName
@@ -563,7 +583,8 @@
                 + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
                 + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled
                 + " mSuppressesSpellChecker=" + mSuppressesSpellChecker
-                + " mShowInInputMethodPicker=" + mShowInInputMethodPicker);
+                + " mShowInInputMethodPicker=" + mShowInInputMethodPicker
+                + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting);
         pw.println(prefix + "mIsDefaultResId=0x"
                 + Integer.toHexString(mIsDefaultResId));
         pw.println(prefix + "Service:");
@@ -667,6 +688,7 @@
         mService.writeToParcel(dest, flags);
         mSubtypes.writeToParcel(dest);
         dest.writeInt(mHandledConfigChanges);
+        dest.writeBoolean(mSupportsStylusHandwriting);
     }
 
     /**
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
new file mode 100644
index 0000000..48af7b9
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
@@ -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 android.view.selectiontoolbar;
+
+import android.view.selectiontoolbar.ToolbarMenuItem;
+import android.view.selectiontoolbar.WidgetInfo;
+
+/**
+ * Binder interface to notify the selection toolbar events from one process to the other.
+ * @hide
+ */
+oneway interface ISelectionToolbarCallback {
+    void onShown(in WidgetInfo info);
+    void onHidden(long widgetToken);
+    void onDismissed(long widgetToken);
+    void onWidgetUpdated(in WidgetInfo info);
+    void onMenuItemClicked(in ToolbarMenuItem item);
+    void onError(int errorCode);
+}
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
new file mode 100644
index 0000000..4a647ad
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
@@ -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 android.view.selectiontoolbar;
+
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+
+/**
+ * Mediator between apps and selection toolbar service implementation.
+ *
+ * @hide
+ */
+oneway interface ISelectionToolbarManager {
+    void showToolbar(in ShowInfo showInfo, in ISelectionToolbarCallback callback, int userId);
+    void hideToolbar(long widgetToken, int userId);
+    void dismissToolbar(long widgetToken, int userId);
+}
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/OWNERS b/core/java/android/view/selectiontoolbar/OWNERS
new file mode 100644
index 0000000..5500b92
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 709498
+
+augale@google.com
+joannechung@google.com
+licha@google.com
+lpeter@google.com
+svetoslavganov@google.com
+toki@google.com
+tonymak@google.com
+tymtsai@google.com
\ No newline at end of file
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/view/selectiontoolbar/SelectionContext.aidl
similarity index 87%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/view/selectiontoolbar/SelectionContext.aidl
index 0abc7ec..5206831 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/view/selectiontoolbar/SelectionContext.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.view.selectiontoolbar;
 
-parcelable TsRequest;
+/**
+ * @hide
+ */
+parcelable SelectionContext;
diff --git a/core/java/android/view/selectiontoolbar/SelectionContext.java b/core/java/android/view/selectiontoolbar/SelectionContext.java
new file mode 100644
index 0000000..21b8d8f
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/SelectionContext.java
@@ -0,0 +1,248 @@
+/*
+ * 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.selectiontoolbar;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * The class holds information for a selection.
+ *
+ * @hide
+ */
+@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true)
+public final class SelectionContext implements Parcelable {
+
+    /**
+     * The start index of a selection.
+     */
+    private final int mStartIndex;
+
+    /**
+     * The end index of a selection.
+     */
+    private final int mEndIndex;
+
+
+
+    // 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/view/selectiontoolbar/SelectionContext.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 */ SelectionContext(
+            int startIndex,
+            int endIndex) {
+        this.mStartIndex = startIndex;
+        this.mEndIndex = endIndex;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The start index of a selection.
+     */
+    @DataClass.Generated.Member
+    public int getStartIndex() {
+        return mStartIndex;
+    }
+
+    /**
+     * The end index of a selection.
+     */
+    @DataClass.Generated.Member
+    public int getEndIndex() {
+        return mEndIndex;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "SelectionContext { " +
+                "startIndex = " + mStartIndex + ", " +
+                "endIndex = " + mEndIndex +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(SelectionContext other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        SelectionContext that = (SelectionContext) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mStartIndex == that.mStartIndex
+                && mEndIndex == that.mEndIndex;
+    }
+
+    @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 + mStartIndex;
+        _hash = 31 * _hash + mEndIndex;
+        return _hash;
+    }
+
+    @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) { ... }
+
+        dest.writeInt(mStartIndex);
+        dest.writeInt(mEndIndex);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ SelectionContext(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int startIndex = in.readInt();
+        int endIndex = in.readInt();
+
+        this.mStartIndex = startIndex;
+        this.mEndIndex = endIndex;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<SelectionContext> CREATOR
+            = new Parcelable.Creator<SelectionContext>() {
+        @Override
+        public SelectionContext[] newArray(int size) {
+            return new SelectionContext[size];
+        }
+
+        @Override
+        public SelectionContext createFromParcel(@NonNull android.os.Parcel in) {
+            return new SelectionContext(in);
+        }
+    };
+
+    /**
+     * A builder for {@link SelectionContext}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private int mStartIndex;
+        private int mEndIndex;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param startIndex
+         *   The start index of a selection.
+         * @param endIndex
+         *   The end index of a selection.
+         */
+        public Builder(
+                int startIndex,
+                int endIndex) {
+            mStartIndex = startIndex;
+            mEndIndex = endIndex;
+        }
+
+        /**
+         * The start index of a selection.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setStartIndex(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mStartIndex = value;
+            return this;
+        }
+
+        /**
+         * The end index of a selection.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setEndIndex(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2;
+            mEndIndex = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull SelectionContext build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x4; // Mark builder used
+
+            SelectionContext o = new SelectionContext(
+                    mStartIndex,
+                    mEndIndex);
+            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 = 1639488292248L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/SelectionContext.java",
+            inputSignatures = "private final  int mStartIndex\nprivate final  int mEndIndex\nclass SelectionContext extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
new file mode 100644
index 0000000..60688ea
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
@@ -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 android.view.selectiontoolbar;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+
+import java.util.Objects;
+
+/**
+ * The {@link SelectionToolbarManager} class provides ways for apps to control the
+ * selection toolbar.
+ *
+ * @hide
+ */
+@SystemService(Context.SELECTION_TOOLBAR_SERVICE)
+public final class SelectionToolbarManager {
+
+    private static final String TAG = "SelectionToolbar";
+
+    /**
+     * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell
+     * setprop log.tag.UiTranslation DEBUG".
+     */
+    public static final String LOG_TAG = "SelectionToolbar";
+
+
+    @NonNull
+    private final Context mContext;
+    private final ISelectionToolbarManager mService;
+
+    public SelectionToolbarManager(@NonNull Context context,
+            @NonNull ISelectionToolbarManager service) {
+        mContext = Objects.requireNonNull(context);
+        mService = service;
+    }
+
+    /**
+     * Request to show selection toolbar for a given View.
+     */
+    public void showToolbar(@NonNull ShowInfo showInfo,
+            @NonNull ISelectionToolbarCallback callback) {
+        try {
+            Objects.requireNonNull(showInfo);
+            Objects.requireNonNull(callback);
+            mService.showToolbar(showInfo, callback, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Request to hide selection toolbar.
+     */
+    public void hideToolbar(long widgetToken) {
+        try {
+            mService.hideToolbar(widgetToken, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Dismiss to dismiss selection toolbar.
+     */
+    public void dismissToolbar(long widgetToken) {
+        try {
+            mService.dismissToolbar(widgetToken, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/view/selectiontoolbar/ShowInfo.aidl
similarity index 88%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/view/selectiontoolbar/ShowInfo.aidl
index 0abc7ec..dce9c15 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.view.selectiontoolbar;
 
-parcelable TsRequest;
+/**
+ * @hide
+ */
+parcelable ShowInfo;
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
new file mode 100644
index 0000000..bbbd5c0
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/ShowInfo.java
@@ -0,0 +1,169 @@
+/*
+ * 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.selectiontoolbar;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+
+/**
+ * The class holds menu information for render service to render the selection toolbar.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public final class ShowInfo implements Parcelable {
+    /**
+     * The token that is used to identify the selection toolbar. This is initially set to 0
+     * until a selection toolbar has been created for the showToolbar request.
+     */
+    private final long mWidgetToken;
+
+    // TODO: add members when the code really uses it
+
+
+
+
+    // 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/view/selectiontoolbar/ShowInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new ShowInfo.
+     *
+     * @param widgetToken
+     *   The token that is used to identify the selection toolbar.
+     */
+    @DataClass.Generated.Member
+    public ShowInfo(
+            long widgetToken) {
+        this.mWidgetToken = widgetToken;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The token that is used to identify the selection toolbar.
+     */
+    @DataClass.Generated.Member
+    public long getWidgetToken() {
+        return mWidgetToken;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ShowInfo { " +
+                "widgetToken = " + mWidgetToken +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(ShowInfo other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        ShowInfo that = (ShowInfo) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mWidgetToken == that.mWidgetToken;
+    }
+
+    @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 + Long.hashCode(mWidgetToken);
+        return _hash;
+    }
+
+    @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) { ... }
+
+        dest.writeLong(mWidgetToken);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ShowInfo(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        long widgetToken = in.readLong();
+
+        this.mWidgetToken = widgetToken;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ShowInfo> CREATOR
+            = new Parcelable.Creator<ShowInfo>() {
+        @Override
+        public ShowInfo[] newArray(int size) {
+            return new ShowInfo[size];
+        }
+
+        @Override
+        public ShowInfo createFromParcel(@NonNull android.os.Parcel in) {
+            return new ShowInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1639488262761L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
+            inputSignatures = "private final  long mWidgetToken\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
similarity index 87%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
index 0abc7ec..711a85a 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.view.selectiontoolbar;
 
-parcelable TsRequest;
+/**
+ * @hide
+ */
+parcelable ToolbarMenuItem;
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
new file mode 100644
index 0000000..5af232c
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.selectiontoolbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * The menu item that is used to show the selection toolbar.
+ *
+ * @hide
+ */
+@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true)
+public final class ToolbarMenuItem implements Parcelable {
+
+    /**
+     * The id of the menu item.
+     */
+    private final int mItemId;
+
+
+
+    // 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/view/selectiontoolbar/ToolbarMenuItem.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 */ ToolbarMenuItem(
+            int itemId) {
+        this.mItemId = itemId;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The id of the menu item.
+     */
+    @DataClass.Generated.Member
+    public int getItemId() {
+        return mItemId;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "ToolbarMenuItem { " +
+                "itemId = " + mItemId +
+        " }";
+    }
+
+    @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(ToolbarMenuItem other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        ToolbarMenuItem that = (ToolbarMenuItem) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mItemId == that.mItemId;
+    }
+
+    @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 + mItemId;
+        return _hash;
+    }
+
+    @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) { ... }
+
+        dest.writeInt(mItemId);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ ToolbarMenuItem(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int itemId = in.readInt();
+
+        this.mItemId = itemId;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<ToolbarMenuItem> CREATOR
+            = new Parcelable.Creator<ToolbarMenuItem>() {
+        @Override
+        public ToolbarMenuItem[] newArray(int size) {
+            return new ToolbarMenuItem[size];
+        }
+
+        @Override
+        public ToolbarMenuItem createFromParcel(@NonNull android.os.Parcel in) {
+            return new ToolbarMenuItem(in);
+        }
+    };
+
+    /**
+     * A builder for {@link ToolbarMenuItem}
+     */
+    @SuppressWarnings("WeakerAccess")
+    @DataClass.Generated.Member
+    public static final class Builder {
+
+        private int mItemId;
+
+        private long mBuilderFieldsSet = 0L;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @param itemId
+         *   The id of the menu item.
+         */
+        public Builder(
+                int itemId) {
+            mItemId = itemId;
+        }
+
+        /**
+         * The id of the menu item.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setItemId(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x1;
+            mItemId = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        public @NonNull ToolbarMenuItem build() {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x2; // Mark builder used
+
+            ToolbarMenuItem o = new ToolbarMenuItem(
+                    mItemId);
+            return o;
+        }
+
+        private void checkNotUsed() {
+            if ((mBuilderFieldsSet & 0x2) != 0) {
+                throw new IllegalStateException(
+                        "This Builder should not be reused. Use a new Builder instance instead");
+            }
+        }
+    }
+
+    @DataClass.Generated(
+            time = 1639488328542L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java",
+            inputSignatures = "private final  int mItemId\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/android/view/selectiontoolbar/WidgetInfo.aidl
similarity index 88%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/android/view/selectiontoolbar/WidgetInfo.aidl
index 0abc7ec..1057c51 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/android/view/selectiontoolbar/WidgetInfo.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package android.view.selectiontoolbar;
 
-parcelable TsRequest;
+/**
+ * @hide
+ */
+parcelable WidgetInfo;
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java
new file mode 100644
index 0000000..961d8ac
--- /dev/null
+++ b/core/java/android/view/selectiontoolbar/WidgetInfo.java
@@ -0,0 +1,168 @@
+/*
+ * 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.selectiontoolbar;
+
+import android.annotation.NonNull;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * The class holds the rendered content and the related information from the render service to
+ * be used to show on the selection toolbar.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public final class WidgetInfo implements Parcelable {
+
+    /**
+     * The token that is used to identify the selection toolbar.
+     */
+    private final long mWidgetToken;
+
+    // TODO: add members when the code really uses it
+
+
+
+    // 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/view/selectiontoolbar/WidgetInfo.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new WidgetInfo.
+     *
+     * @param widgetToken
+     *   The token that is used to identify the selection toolbar.
+     */
+    @DataClass.Generated.Member
+    public WidgetInfo(
+            long widgetToken) {
+        this.mWidgetToken = widgetToken;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The token that is used to identify the selection toolbar.
+     */
+    @DataClass.Generated.Member
+    public long getWidgetToken() {
+        return mWidgetToken;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "WidgetInfo { " +
+                "widgetToken = " + mWidgetToken +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@android.annotation.Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(WidgetInfo other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        WidgetInfo that = (WidgetInfo) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mWidgetToken == that.mWidgetToken;
+    }
+
+    @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 + Long.hashCode(mWidgetToken);
+        return _hash;
+    }
+
+    @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) { ... }
+
+        dest.writeLong(mWidgetToken);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ WidgetInfo(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        long widgetToken = in.readLong();
+
+        this.mWidgetToken = widgetToken;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<WidgetInfo> CREATOR
+            = new Parcelable.Creator<WidgetInfo>() {
+        @Override
+        public WidgetInfo[] newArray(int size) {
+            return new WidgetInfo[size];
+        }
+
+        @Override
+        public WidgetInfo createFromParcel(@NonNull android.os.Parcel in) {
+            return new WidgetInfo(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1639488254020L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java",
+            inputSignatures = "private final  long mWidgetToken\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java
index bf0409d..a4a5a1e 100644
--- a/core/java/android/view/textclassifier/ConversationAction.java
+++ b/core/java/android/view/textclassifier/ConversationAction.java
@@ -141,7 +141,7 @@
 
     private ConversationAction(Parcel in) {
         mType = in.readString();
-        mAction = in.readParcelable(null);
+        mAction = in.readParcelable(null, android.app.RemoteAction.class);
         mTextReply = in.readCharSequence();
         mScore = in.readFloat();
         mExtras = in.readBundle();
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 6ad5cb9..7a6a3cd 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -149,7 +149,7 @@
         }
 
         private Message(Parcel in) {
-            mAuthor = in.readParcelable(null);
+            mAuthor = in.readParcelable(null, android.app.Person.class);
             mReferenceTime =
                     in.readInt() == 0
                             ? null
@@ -331,13 +331,13 @@
 
         private static Request readFromParcel(Parcel in) {
             List<Message> conversation = new ArrayList<>();
-            in.readParcelableList(conversation, null);
-            TextClassifier.EntityConfig typeConfig = in.readParcelable(null);
+            in.readParcelableList(conversation, null, android.view.textclassifier.ConversationActions.Message.class);
+            TextClassifier.EntityConfig typeConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class);
             int maxSuggestions = in.readInt();
             List<String> hints = new ArrayList<>();
             in.readStringList(hints);
             Bundle extras = in.readBundle();
-            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             Request request = new Request(
                     conversation,
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 858825b..b347010 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -172,7 +172,7 @@
         mEnd = in.readInt();
         mSmartStart = in.readInt();
         mSmartEnd = in.readInt();
-        mSystemTcMetadata = in.readParcelable(null);
+        mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
     }
 
     @Override
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 7db35d4..8b04d35 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -713,12 +713,12 @@
             final CharSequence text = in.readCharSequence();
             final int startIndex = in.readInt();
             final int endIndex = in.readInt();
-            final LocaleList defaultLocales = in.readParcelable(null);
+            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
             final Bundle extras = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             final Request request = new Request(text, startIndex, endIndex,
                     defaultLocales, referenceTime, extras);
diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java
index 5d5683f..3a50809 100644
--- a/core/java/android/view/textclassifier/TextClassificationContext.java
+++ b/core/java/android/view/textclassifier/TextClassificationContext.java
@@ -159,7 +159,7 @@
         mPackageName = in.readString();
         mWidgetType = in.readString();
         mWidgetVersion = in.readString();
-        mSystemTcMetadata = in.readParcelable(null);
+        mSystemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<TextClassificationContext> CREATOR =
diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java
index 90667cf..195565c 100644
--- a/core/java/android/view/textclassifier/TextClassifierEvent.java
+++ b/core/java/android/view/textclassifier/TextClassifierEvent.java
@@ -189,7 +189,7 @@
         mEventCategory = in.readInt();
         mEventType = in.readInt();
         mEntityTypes = in.readStringArray();
-        mEventContext = in.readParcelable(null);
+        mEventContext = in.readParcelable(null, android.view.textclassifier.TextClassificationContext.class);
         mResultId = in.readString();
         mEventIndex = in.readInt();
         int scoresLength = in.readInt();
diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java
index 604979b..67167c6 100644
--- a/core/java/android/view/textclassifier/TextLanguage.java
+++ b/core/java/android/view/textclassifier/TextLanguage.java
@@ -295,7 +295,7 @@
         private static Request readFromParcel(Parcel in) {
             final CharSequence text = in.readCharSequence();
             final Bundle extra = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             final Request request = new Request(text, extra);
             request.setSystemTextClassifierMetadata(systemTcMetadata);
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index dea3a90..445e9ec 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -558,13 +558,13 @@
 
         private static Request readFromParcel(Parcel in) {
             final String text = in.readString();
-            final LocaleList defaultLocales = in.readParcelable(null);
-            final EntityConfig entityConfig = in.readParcelable(null);
+            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
+            final EntityConfig entityConfig = in.readParcelable(null, android.view.textclassifier.TextClassifier.EntityConfig.class);
             final Bundle extras = in.readBundle();
             final String referenceTimeString = in.readString();
             final ZonedDateTime referenceTime = referenceTimeString == null
                     ? null : ZonedDateTime.parse(referenceTimeString);
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
 
             final Request request = new Request(text, defaultLocales, entityConfig,
                     /* legacyFallback= */ true, referenceTime, extras);
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index c1913f6..dda0fcdd 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -489,9 +489,9 @@
             final CharSequence text = in.readCharSequence();
             final int startIndex = in.readInt();
             final int endIndex = in.readInt();
-            final LocaleList defaultLocales = in.readParcelable(null);
+            final LocaleList defaultLocales = in.readParcelable(null, android.os.LocaleList.class);
             final Bundle extras = in.readBundle();
-            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null);
+            final SystemTextClassifierMetadata systemTcMetadata = in.readParcelable(null, android.view.textclassifier.SystemTextClassifierMetadata.class);
             final boolean includeTextClassification = in.readBoolean();
 
             final Request request = new Request(text, startIndex, endIndex, defaultLocales,
@@ -548,6 +548,6 @@
         mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in);
         mId = in.readString();
         mExtras = in.readBundle();
-        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader());
+        mTextClassification = in.readParcelable(TextClassification.class.getClassLoader(), android.view.textclassifier.TextClassification.class);
     }
 }
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index 0d41851..027edc2 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -255,9 +255,9 @@
 
         int flags = in.readInt();
         List<TranslationRequestValue> translationRequestValues = new ArrayList<>();
-        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader());
+        in.readParcelableList(translationRequestValues, TranslationRequestValue.class.getClassLoader(), android.view.translation.TranslationRequestValue.class);
         List<ViewTranslationRequest> viewTranslationRequests = new ArrayList<>();
-        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader());
+        in.readParcelableList(viewTranslationRequests, ViewTranslationRequest.class.getClassLoader(), android.view.translation.ViewTranslationRequest.class);
 
         this.mFlags = flags;
 
diff --git a/core/java/android/view/translation/TranslationSpec.java b/core/java/android/view/translation/TranslationSpec.java
index efc3d8b..76dda5f 100644
--- a/core/java/android/view/translation/TranslationSpec.java
+++ b/core/java/android/view/translation/TranslationSpec.java
@@ -64,7 +64,7 @@
     }
 
     static ULocale unparcelLocale(Parcel in) {
-        return (ULocale) in.readSerializable();
+        return (ULocale) in.readSerializable(android.icu.util.ULocale.class.getClassLoader(), android.icu.util.ULocale.class);
     }
 
     /**
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 357dbc0..2702c2d 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -437,7 +437,10 @@
                     if (view.getViewTranslationResponse() != null
                             && view.getViewTranslationResponse().equals(response)) {
                         if (callback instanceof TextViewTranslationCallback) {
-                            if (((TextViewTranslationCallback) callback).isShowingTranslation()) {
+                            TextViewTranslationCallback textViewCallback =
+                                    (TextViewTranslationCallback) callback;
+                            if (textViewCallback.isShowingTranslation()
+                                    || textViewCallback.isAnimationRunning()) {
                                 if (DEBUG) {
                                     Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
                                             + ". Ignoring.");
@@ -623,7 +626,7 @@
 
     private void addViewIfNeeded(IntArray sourceViewIds, View view) {
         final AutofillId autofillId = view.getAutofillId();
-        if ((sourceViewIds.indexOf(autofillId.getViewId()) >= 0)
+        if (autofillId != null && (sourceViewIds.indexOf(autofillId.getViewId()) >= 0)
                 && !mViews.containsKey(autofillId)) {
             mViews.put(autofillId, new WeakReference<>(view));
         }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a45a91e..414a7f1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3681,14 +3681,14 @@
                                     if (!mEdgeGlowBottom.isFinished()) {
                                         mEdgeGlowBottom.onRelease();
                                     }
-                                    invalidateTopGlow();
+                                    invalidateEdgeEffects();
                                 } else if (incrementalDeltaY < 0) {
                                     mEdgeGlowBottom.onPullDistance((float) overscroll / getHeight(),
                                             1.f - (float) x / getWidth());
                                     if (!mEdgeGlowTop.isFinished()) {
                                         mEdgeGlowTop.onRelease();
                                     }
-                                    invalidateBottomGlow();
+                                    invalidateEdgeEffects();
                                 }
                             }
                         }
@@ -3728,7 +3728,7 @@
                             if (!mEdgeGlowBottom.isFinished()) {
                                 mEdgeGlowBottom.onRelease();
                             }
-                            invalidateTopGlow();
+                            invalidateEdgeEffects();
                         } else if (rawDeltaY < 0) {
                             mEdgeGlowBottom.onPullDistance(
                                     (float) -overScrollDistance / getHeight(),
@@ -3736,7 +3736,7 @@
                             if (!mEdgeGlowTop.isFinished()) {
                                 mEdgeGlowTop.onRelease();
                             }
-                            invalidateBottomGlow();
+                            invalidateEdgeEffects();
                         }
                     }
                 }
@@ -3782,17 +3782,21 @@
         // First allow releasing existing overscroll effect:
         float consumed = 0;
         if (mEdgeGlowTop.getDistance() != 0) {
-            consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
-                    (float) x / getWidth());
-            if (consumed != 0f) {
-                invalidateTopGlow();
+            if (canScrollUp()) {
+                mEdgeGlowTop.onRelease();
+            } else {
+                consumed = mEdgeGlowTop.onPullDistance((float) deltaY / getHeight(),
+                        (float) x / getWidth());
             }
+            invalidateEdgeEffects();
         } else if (mEdgeGlowBottom.getDistance() != 0) {
-            consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
-                    1f - (float) x / getWidth());
-            if (consumed != 0f) {
-                invalidateBottomGlow();
+            if (canScrollDown()) {
+                mEdgeGlowBottom.onRelease();
+            } else {
+                consumed = -mEdgeGlowBottom.onPullDistance((float) -deltaY / getHeight(),
+                        1f - (float) x / getWidth());
             }
+            invalidateEdgeEffects();
         }
         int pixelsConsumed = Math.round(consumed * getHeight());
         return deltaY - pixelsConsumed;
@@ -3802,30 +3806,16 @@
      * @return <code>true</code> if either the top or bottom edge glow is currently active or
      * <code>false</code> if it has no value to release.
      */
-    private boolean isGlowActive() {
-        return mEdgeGlowBottom.getDistance() != 0 || mEdgeGlowTop.getDistance() != 0;
+    private boolean doesTouchStopStretch() {
+        return (mEdgeGlowBottom.getDistance() != 0 && !canScrollDown())
+                || (mEdgeGlowTop.getDistance() != 0 && !canScrollUp());
     }
 
-    private void invalidateTopGlow() {
+    private void invalidateEdgeEffects() {
         if (!shouldDisplayEdgeEffects()) {
             return;
         }
-        final boolean clipToPadding = getClipToPadding();
-        final int top = clipToPadding ? mPaddingTop : 0;
-        final int left = clipToPadding ? mPaddingLeft : 0;
-        final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
-        invalidate(left, top, right, top + mEdgeGlowTop.getMaxHeight());
-    }
-
-    private void invalidateBottomGlow() {
-        if (!shouldDisplayEdgeEffects()) {
-            return;
-        }
-        final boolean clipToPadding = getClipToPadding();
-        final int bottom = clipToPadding ? getHeight() - mPaddingBottom : getHeight();
-        final int left = clipToPadding ? mPaddingLeft : 0;
-        final int right = clipToPadding ? getWidth() - mPaddingRight : getWidth();
-        invalidate(left, bottom - mEdgeGlowBottom.getMaxHeight(), right, bottom);
+        invalidate();
     }
 
     @Override
@@ -4468,7 +4458,7 @@
                 final int edgeY = Math.min(0, scrollY + mFirstPositionDistanceGuess) + translateY;
                 canvas.translate(translateX, edgeY);
                 if (mEdgeGlowTop.draw(canvas)) {
-                    invalidateTopGlow();
+                    invalidateEdgeEffects();
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4482,7 +4472,7 @@
                 canvas.translate(edgeX, edgeY);
                 canvas.rotate(180, width, 0);
                 if (mEdgeGlowBottom.draw(canvas)) {
-                    invalidateBottomGlow();
+                    invalidateEdgeEffects();
                 }
                 canvas.restoreToCount(restoreCount);
             }
@@ -4572,7 +4562,7 @@
                 mActivePointerId = ev.getPointerId(0);
 
                 int motionPosition = findMotionRow(y);
-                if (isGlowActive()) {
+                if (doesTouchStopStretch()) {
                     // Pressed during edge effect, so this is considered the same as a fling catch.
                     touchMode = mTouchMode = TOUCH_MODE_FLING;
                 } else if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
@@ -6579,7 +6569,7 @@
      */
     public void setBottomEdgeEffectColor(@ColorInt int color) {
         mEdgeGlowBottom.setColor(color);
-        invalidateBottomGlow();
+        invalidateEdgeEffects();
     }
 
     /**
@@ -6593,7 +6583,7 @@
      */
     public void setTopEdgeEffectColor(@ColorInt int color) {
         mEdgeGlowTop.setColor(color);
-        invalidateTopGlow();
+        invalidateEdgeEffects();
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index a8bf50e..9695208 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -69,6 +69,7 @@
 import android.text.Spannable;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
+import android.text.SpannedString;
 import android.text.StaticLayout;
 import android.text.TextUtils;
 import android.text.method.KeyListener;
@@ -1848,7 +1849,7 @@
         if (mHasPendingRestartInputForSetText) {
             final InputMethodManager imm = getInputMethodManager();
             if (imm != null) {
-                imm.invalidateInput(mTextView);
+                imm.restartInput(mTextView);
             }
             mHasPendingRestartInputForSetText = false;
         }
@@ -2513,7 +2514,7 @@
      * the current cursor position or selection range. This method is consistent with the
      * method to show suggestions {@link SuggestionsPopupWindow#updateSuggestions}.
      */
-    private boolean shouldOfferToShowSuggestions() {
+    boolean shouldOfferToShowSuggestions() {
         CharSequence text = mTextView.getText();
         if (!(text instanceof Spannable)) return false;
 
@@ -4120,8 +4121,15 @@
                 mSuggestionRangeSpan.setBackgroundColor(
                         (underlineColor & 0x00FFFFFF) + (newAlpha << 24));
             }
+            boolean sendAccessibilityEvent = mTextView.isVisibleToAccessibility();
+            CharSequence beforeText = sendAccessibilityEvent
+                    ? new SpannedString(spannable, true) : null;
             spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            if (sendAccessibilityEvent) {
+                mTextView.sendAccessibilityEventTypeViewTextChanged(
+                        beforeText, spanUnionStart, spanUnionEnd);
+            }
 
             mSuggestionsAdapter.notifyDataSetChanged();
             return true;
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 51869d4..e243aae 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -1309,7 +1309,7 @@
         private SavedState(Parcel in) {
             super(in);
             expandedGroupMetadataList = new ArrayList<ExpandableListConnector.GroupMetadata>();
-            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader());
+            in.readList(expandedGroupMetadataList, ExpandableListConnector.class.getClassLoader(), android.widget.ExpandableListConnector.GroupMetadata.class);
         }
 
         @Override
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3d4d9ec..a5f3feb 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1482,7 +1482,7 @@
 
         SetRippleDrawableColor(Parcel parcel) {
             viewId = parcel.readInt();
-            mColorStateList = parcel.readParcelable(null);
+            mColorStateList = parcel.readParcelable(null, android.content.res.ColorStateList.class);
         }
 
         public void writeToParcel(Parcel dest, int flags) {
@@ -3696,18 +3696,21 @@
     }
 
     private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) {
+        if (hierarchyRoot == null) {
+            mBitmapCache = src.mBitmapCache;
+            mApplicationInfoCache = src.mApplicationInfoCache;
+        } else {
+            mBitmapCache = hierarchyRoot.mBitmapCache;
+            mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
+        }
         if (hierarchyRoot == null || src.mIsRoot) {
             // If there's no provided root, or if src was itself a root, then this RemoteViews is
             // the root of the new hierarchy.
             mIsRoot = true;
-            mBitmapCache = new BitmapCache();
-            mApplicationInfoCache = new ApplicationInfoCache();
             hierarchyRoot = this;
         } else {
             // Otherwise, we're a descendant in the hierarchy.
             mIsRoot = false;
-            mBitmapCache = hierarchyRoot.mBitmapCache;
-            mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache;
         }
         mApplication = src.mApplication;
         mLayoutId = src.mLayoutId;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 496fa67..0143401 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12105,6 +12105,9 @@
             if (canCut()) {
                 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
             }
+            if (canReplace()) {
+                info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_SUGGESTIONS);
+            }
             if (canShare()) {
                 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
                         ACCESSIBILITY_ACTION_SHARE,
@@ -12419,6 +12422,11 @@
             }
             return false;
             default: {
+                // New ids have static blocks to assign values, so they can't be used in a case
+                // block.
+                if (action == R.id.accessibilityActionShowSuggestions) {
+                    return isFocused() && canReplace() && onTextContextMenuItem(ID_REPLACE);
+                }
                 return super.performAccessibilityActionInternal(action, arguments);
             }
         }
@@ -13005,6 +13013,15 @@
         return false;
     }
 
+    boolean canReplace() {
+        if (hasPasswordTransformationMethod()) {
+            return false;
+        }
+
+        return (mText.length() > 0) && (mText instanceof Editable) && (mEditor != null)
+                && isSuggestionsEnabled() && mEditor.shouldOfferToShowSuggestions();
+    }
+
     boolean canShare() {
         if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
             return false;
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 4a78f3e..942be21 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -46,6 +46,7 @@
 
     private TranslationTransformationMethod mTranslationTransformation;
     private boolean mIsShowingTranslation = false;
+    private boolean mAnimationRunning = false;
     private boolean mIsTextPaddingEnabled = false;
     private CharSequence mPaddedText;
     private int mAnimationDurationMillis = 250; // default value
@@ -92,6 +93,7 @@
                 (TextView) view,
                 () -> {
                     mIsShowingTranslation = true;
+                    mAnimationRunning = false;
                     // TODO(b/178353965): well-handle setTransformationMethod.
                     ((TextView) view).setTransformationMethod(transformation);
                 });
@@ -124,6 +126,7 @@
                     (TextView) view,
                     () -> {
                         mIsShowingTranslation = false;
+                        mAnimationRunning = false;
                         ((TextView) view).setTransformationMethod(transformation);
                     });
             if (!TextUtils.isEmpty(mContentDescription)) {
@@ -162,6 +165,13 @@
         return mIsShowingTranslation;
     }
 
+    /**
+     * Returns whether the view is running animation to show or hide the translation.
+     */
+    public boolean isAnimationRunning() {
+        return mAnimationRunning;
+    }
+
     @Override
     public void enableContentPadding() {
         mIsTextPaddingEnabled = true;
@@ -230,6 +240,7 @@
             mAnimator.end();
             // Note: mAnimator is now null; do not use again here.
         }
+        mAnimationRunning = true;
         int fadedOutColor = colorWithAlpha(view.getCurrentTextColor(), 0);
         mAnimator = ValueAnimator.ofArgb(view.getCurrentTextColor(), fadedOutColor);
         mAnimator.addUpdateListener(
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 1ad0452..4399207 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -44,4 +44,10 @@
      * Unregisters remote animations per transition type for the organizer.
      */
     void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+
+    /**
+      * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+      * only occupies a portion of Task bounds.
+      */
+    boolean isActivityEmbedded(in IBinder activityToken);
 }
diff --git a/core/java/android/window/PictureInPictureSurfaceTransaction.java b/core/java/android/window/PictureInPictureSurfaceTransaction.java
index dbf7eb3..2bf2f319 100644
--- a/core/java/android/window/PictureInPictureSurfaceTransaction.java
+++ b/core/java/android/window/PictureInPictureSurfaceTransaction.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Matrix;
+import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -34,9 +35,10 @@
  * @hide
  */
 public final class PictureInPictureSurfaceTransaction implements Parcelable {
+    private static final float NOT_SET = -1f;
 
-    public final float mPositionX;
-    public final float mPositionY;
+    public final float mAlpha;
+    public final PointF mPosition;
 
     public final float[] mFloat9;
 
@@ -45,33 +47,37 @@
 
     public final float mCornerRadius;
 
-    private final Rect mWindowCrop = new Rect();
+    private final Rect mWindowCrop;
 
-    public PictureInPictureSurfaceTransaction(Parcel in) {
-        mPositionX = in.readFloat();
-        mPositionY = in.readFloat();
+    private PictureInPictureSurfaceTransaction(Parcel in) {
+        mAlpha = in.readFloat();
+        mPosition = in.readTypedObject(PointF.CREATOR);
         mFloat9 = new float[9];
         in.readFloatArray(mFloat9);
         mRotation = in.readFloat();
         mCornerRadius = in.readFloat();
-        mWindowCrop.set(Objects.requireNonNull(in.readTypedObject(Rect.CREATOR)));
+        mWindowCrop = in.readTypedObject(Rect.CREATOR);
     }
 
-    public PictureInPictureSurfaceTransaction(float positionX, float positionY,
-            float[] float9, float rotation, float cornerRadius,
+    private PictureInPictureSurfaceTransaction(float alpha, @Nullable PointF position,
+            @Nullable float[] float9, float rotation, float cornerRadius,
             @Nullable Rect windowCrop) {
-        mPositionX = positionX;
-        mPositionY = positionY;
-        mFloat9 = Arrays.copyOf(float9, 9);
-        mRotation = rotation;
-        mCornerRadius = cornerRadius;
-        if (windowCrop != null) {
-            mWindowCrop.set(windowCrop);
+        mAlpha = alpha;
+        mPosition = position;
+        if (float9 == null) {
+            mFloat9 = new float[9];
+            Matrix.IDENTITY_MATRIX.getValues(mFloat9);
+            mRotation = 0;
+        } else {
+            mFloat9 = Arrays.copyOf(float9, 9);
+            mRotation = rotation;
         }
+        mCornerRadius = cornerRadius;
+        mWindowCrop = (windowCrop == null) ? null : new Rect(windowCrop);
     }
 
     public PictureInPictureSurfaceTransaction(PictureInPictureSurfaceTransaction other) {
-        this(other.mPositionX, other.mPositionY,
+        this(other.mAlpha, other.mPosition,
                 other.mFloat9, other.mRotation, other.mCornerRadius, other.mWindowCrop);
     }
 
@@ -82,13 +88,18 @@
         return matrix;
     }
 
+    /** @return {@code true} if this transaction contains setting corner radius. */
+    public boolean hasCornerRadiusSet() {
+        return mCornerRadius > 0;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (!(o instanceof PictureInPictureSurfaceTransaction)) return false;
         PictureInPictureSurfaceTransaction that = (PictureInPictureSurfaceTransaction) o;
-        return Objects.equals(mPositionX, that.mPositionX)
-                && Objects.equals(mPositionY, that.mPositionY)
+        return Objects.equals(mAlpha, that.mAlpha)
+                && Objects.equals(mPosition, that.mPosition)
                 && Arrays.equals(mFloat9, that.mFloat9)
                 && Objects.equals(mRotation, that.mRotation)
                 && Objects.equals(mCornerRadius, that.mCornerRadius)
@@ -97,7 +108,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPositionX, mPositionY, Arrays.hashCode(mFloat9),
+        return Objects.hash(mAlpha, mPosition, Arrays.hashCode(mFloat9),
                 mRotation, mCornerRadius, mWindowCrop);
     }
 
@@ -108,8 +119,8 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeFloat(mPositionX);
-        out.writeFloat(mPositionY);
+        out.writeFloat(mAlpha);
+        out.writeTypedObject(mPosition, 0 /* flags */);
         out.writeFloatArray(mFloat9);
         out.writeFloat(mRotation);
         out.writeFloat(mCornerRadius);
@@ -120,8 +131,8 @@
     public String toString() {
         final Matrix matrix = getMatrix();
         return "PictureInPictureSurfaceTransaction("
-                + " posX=" + mPositionX
-                + " posY=" + mPositionY
+                + " alpha=" + mAlpha
+                + " position=" + mPosition
                 + " matrix=" + matrix.toShortString()
                 + " rotation=" + mRotation
                 + " cornerRadius=" + mCornerRadius
@@ -134,11 +145,20 @@
             @NonNull SurfaceControl surfaceControl,
             @NonNull SurfaceControl.Transaction tx) {
         final Matrix matrix = surfaceTransaction.getMatrix();
-        tx.setMatrix(surfaceControl, matrix, new float[9])
-                .setPosition(surfaceControl,
-                        surfaceTransaction.mPositionX, surfaceTransaction.mPositionY)
-                .setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop)
-                .setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius);
+        tx.setMatrix(surfaceControl, matrix, new float[9]);
+        if (surfaceTransaction.mPosition != null) {
+            tx.setPosition(surfaceControl,
+                    surfaceTransaction.mPosition.x, surfaceTransaction.mPosition.y);
+        }
+        if (surfaceTransaction.mWindowCrop != null) {
+            tx.setWindowCrop(surfaceControl, surfaceTransaction.mWindowCrop);
+        }
+        if (surfaceTransaction.hasCornerRadiusSet()) {
+            tx.setCornerRadius(surfaceControl, surfaceTransaction.mCornerRadius);
+        }
+        if (surfaceTransaction.mAlpha != NOT_SET) {
+            tx.setAlpha(surfaceControl, surfaceTransaction.mAlpha);
+        }
     }
 
     public static final @android.annotation.NonNull Creator<PictureInPictureSurfaceTransaction>
@@ -151,4 +171,44 @@
                     return new PictureInPictureSurfaceTransaction[size];
                 }
             };
+
+    public static class Builder {
+        private float mAlpha = NOT_SET;
+        private PointF mPosition;
+        private float[] mFloat9;
+        private float mRotation;
+        private float mCornerRadius = NOT_SET;
+        private Rect mWindowCrop;
+
+        public Builder setAlpha(float alpha) {
+            mAlpha = alpha;
+            return this;
+        }
+
+        public Builder setPosition(float x, float y) {
+            mPosition = new PointF(x, y);
+            return this;
+        }
+
+        public Builder setTransform(@NonNull float[] float9, float rotation) {
+            mFloat9 = Arrays.copyOf(float9, 9);
+            mRotation = rotation;
+            return this;
+        }
+
+        public Builder setCornerRadius(float cornerRadius) {
+            mCornerRadius = cornerRadius;
+            return this;
+        }
+
+        public Builder setWindowCrop(@NonNull Rect windowCrop) {
+            mWindowCrop = new Rect(windowCrop);
+            return this;
+        }
+
+        public PictureInPictureSurfaceTransaction build() {
+            return new PictureInPictureSurfaceTransaction(mAlpha, mPosition,
+                    mFloat9, mRotation, mCornerRadius, mWindowCrop);
+        }
+    }
 }
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 7e7d370..9c2fde0 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -216,4 +216,17 @@
             return null;
         }
     }
+
+    /**
+     * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+     * only occupies a portion of Task bounds.
+     * @hide
+     */
+    public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
+        try {
+            return getController().isActivityEmbedded(activityToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/window/TransitionRequestInfo.aidl b/core/java/android/window/TransitionRequestInfo.aidl
index d2b9ccf..6f980d4 100644
--- a/core/java/android/window/TransitionRequestInfo.aidl
+++ b/core/java/android/window/TransitionRequestInfo.aidl
@@ -17,3 +17,4 @@
 package android.window;
 
 parcelable TransitionRequestInfo;
+parcelable TransitionRequestInfo.DisplayChange;
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index f770731..e0cdb13 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -18,6 +18,8 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.graphics.Rect;
 import android.os.Parcelable;
 import android.view.WindowManager;
 
@@ -42,6 +44,194 @@
     /** If non-null, a remote-transition associated with the source of this transition. */
     private @Nullable RemoteTransition mRemoteTransition;
 
+    /**
+     * If non-null, this request was triggered by this display change. This will not be complete:
+     * The reliable parts should be flags, rotation start/end (if rotating), and start/end bounds
+     * (if size is changing).
+     */
+    private @Nullable DisplayChange mDisplayChange;
+
+    /** constructor override */
+    public TransitionRequestInfo(
+            @WindowManager.TransitionType int type,
+            @Nullable ActivityManager.RunningTaskInfo triggerTask,
+            @Nullable RemoteTransition remoteTransition) {
+        this(type, triggerTask, remoteTransition, null /* displayChange */);
+    }
+
+    /** Requested change to a display. */
+    @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
+    public static class DisplayChange implements Parcelable {
+        private final int mDisplayId;
+        @Nullable private Rect mStartAbsBounds = null;
+        @Nullable private Rect mEndAbsBounds = null;
+        private int mStartRotation = WindowConfiguration.ROTATION_UNDEFINED;
+        private int mEndRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+        /** Create empty display-change. */
+        public DisplayChange(int displayId) {
+            mDisplayId = displayId;
+        }
+
+        /** Create a display-change representing a rotation. */
+        public DisplayChange(int displayId, int startRotation, int endRotation) {
+            mDisplayId = displayId;
+            mStartRotation = startRotation;
+            mEndRotation = endRotation;
+        }
+
+
+
+        // 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/window/TransitionRequestInfo.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        @DataClass.Generated.Member
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        @DataClass.Generated.Member
+        public @Nullable Rect getStartAbsBounds() {
+            return mStartAbsBounds;
+        }
+
+        @DataClass.Generated.Member
+        public @Nullable Rect getEndAbsBounds() {
+            return mEndAbsBounds;
+        }
+
+        @DataClass.Generated.Member
+        public int getStartRotation() {
+            return mStartRotation;
+        }
+
+        @DataClass.Generated.Member
+        public int getEndRotation() {
+            return mEndRotation;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull DisplayChange setStartAbsBounds(@android.annotation.NonNull Rect value) {
+            mStartAbsBounds = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull DisplayChange setEndAbsBounds(@android.annotation.NonNull Rect value) {
+            mEndAbsBounds = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull DisplayChange setStartRotation( int value) {
+            mStartRotation = value;
+            return this;
+        }
+
+        @DataClass.Generated.Member
+        public @android.annotation.NonNull DisplayChange setEndRotation( int value) {
+            mEndRotation = value;
+            return this;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public String toString() {
+            // You can override field toString logic by defining methods like:
+            // String fieldNameToString() { ... }
+
+            return "DisplayChange { " +
+                    "displayId = " + mDisplayId + ", " +
+                    "startAbsBounds = " + mStartAbsBounds + ", " +
+                    "endAbsBounds = " + mEndAbsBounds + ", " +
+                    "startRotation = " + mStartRotation + ", " +
+                    "endRotation = " + mEndRotation +
+            " }";
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@android.annotation.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 (mStartAbsBounds != null) flg |= 0x2;
+            if (mEndAbsBounds != null) flg |= 0x4;
+            dest.writeByte(flg);
+            dest.writeInt(mDisplayId);
+            if (mStartAbsBounds != null) dest.writeTypedObject(mStartAbsBounds, flags);
+            if (mEndAbsBounds != null) dest.writeTypedObject(mEndAbsBounds, flags);
+            dest.writeInt(mStartRotation);
+            dest.writeInt(mEndRotation);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        protected DisplayChange(@android.annotation.NonNull android.os.Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            int displayId = in.readInt();
+            Rect startAbsBounds = (flg & 0x2) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
+            Rect endAbsBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
+            int startRotation = in.readInt();
+            int endRotation = in.readInt();
+
+            this.mDisplayId = displayId;
+            this.mStartAbsBounds = startAbsBounds;
+            this.mEndAbsBounds = endAbsBounds;
+            this.mStartRotation = startRotation;
+            this.mEndRotation = endRotation;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @android.annotation.NonNull Parcelable.Creator<DisplayChange> CREATOR
+                = new Parcelable.Creator<DisplayChange>() {
+            @Override
+            public DisplayChange[] newArray(int size) {
+                return new DisplayChange[size];
+            }
+
+            @Override
+            public DisplayChange createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+                return new DisplayChange(in);
+            }
+        };
+
+        @DataClass.Generated(
+                time = 1639445520915L,
+                codegenVersion = "1.0.23",
+                sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
+                inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
+        @Deprecated
+        private void __metadata() {}
+
+
+        //@formatter:on
+        // End of generated code
+
+    }
+
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -67,17 +257,23 @@
      *   finish) has caused this transition to occur.
      * @param remoteTransition
      *   If non-null, a remote-transition associated with the source of this transition.
+     * @param displayChange
+     *   If non-null, this request was triggered by this display change. This will not be complete:
+     *   The reliable parts should be flags, rotation start/end (if rotating), and start/end bounds
+     *   (if size is changing).
      */
     @DataClass.Generated.Member
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
-            @Nullable RemoteTransition remoteTransition) {
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable DisplayChange displayChange) {
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
         this.mTriggerTask = triggerTask;
         this.mRemoteTransition = remoteTransition;
+        this.mDisplayChange = displayChange;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -108,6 +304,16 @@
     }
 
     /**
+     * If non-null, this request was triggered by this display change. This will not be complete:
+     * The reliable parts should be flags, rotation start/end (if rotating), and start/end bounds
+     * (if size is changing).
+     */
+    @DataClass.Generated.Member
+    public @Nullable DisplayChange getDisplayChange() {
+        return mDisplayChange;
+    }
+
+    /**
      * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
      * finish) has caused this transition to occur.
      */
@@ -126,6 +332,17 @@
         return this;
     }
 
+    /**
+     * If non-null, this request was triggered by this display change. This will not be complete:
+     * The reliable parts should be flags, rotation start/end (if rotating), and start/end bounds
+     * (if size is changing).
+     */
+    @DataClass.Generated.Member
+    public @android.annotation.NonNull TransitionRequestInfo setDisplayChange(@android.annotation.NonNull DisplayChange value) {
+        mDisplayChange = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -135,7 +352,8 @@
         return "TransitionRequestInfo { " +
                 "type = " + mType + ", " +
                 "triggerTask = " + mTriggerTask + ", " +
-                "remoteTransition = " + mRemoteTransition +
+                "remoteTransition = " + mRemoteTransition + ", " +
+                "displayChange = " + mDisplayChange +
         " }";
     }
 
@@ -148,10 +366,12 @@
         byte flg = 0;
         if (mTriggerTask != null) flg |= 0x2;
         if (mRemoteTransition != null) flg |= 0x4;
+        if (mDisplayChange != null) flg |= 0x8;
         dest.writeByte(flg);
         dest.writeInt(mType);
         if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
         if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
+        if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
     }
 
     @Override
@@ -169,12 +389,14 @@
         int type = in.readInt();
         ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
         RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
+        DisplayChange displayChange = (flg & 0x8) == 0 ? null : (DisplayChange) in.readTypedObject(DisplayChange.CREATOR);
 
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
         this.mTriggerTask = triggerTask;
         this.mRemoteTransition = remoteTransition;
+        this.mDisplayChange = displayChange;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -194,10 +416,10 @@
     };
 
     @DataClass.Generated(
-            time = 1629321632222L,
+            time = 1639445520938L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
-            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 5e75797..4745220e 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -23,6 +23,7 @@
 import android.app.WindowConfiguration;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -435,6 +436,21 @@
     }
 
     /**
+     * Starts activity(s) from a shortcut.
+     * @param callingPackage The package launching the shortcut.
+     * @param shortcutInfo Information about the shortcut to start
+     * @param options bundle containing ActivityOptions for the task's top activity.
+     * @hide
+     */
+    @NonNull
+    public WindowContainerTransaction startShortcut(@NonNull String callingPackage,
+            @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
+        mHierarchyOps.add(HierarchyOp.createForStartShortcut(
+                callingPackage, shortcutInfo, options));
+        return this;
+    }
+
+    /**
      * Creates a new TaskFragment with the given options.
      * @param taskFragmentOptions the options used to create the TaskFragment.
      */
@@ -957,11 +973,16 @@
         public static final int HIERARCHY_OP_TYPE_REPARENT_CHILDREN = 11;
         public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 12;
         public static final int HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS = 13;
+        public static final int HIERARCHY_OP_TYPE_START_SHORTCUT = 14;
 
         // The following key(s) are for use with mLaunchOptions:
         // When launching a task (eg. from recents), this is the taskId to be launched.
         public static final String LAUNCH_KEY_TASK_ID = "android:transaction.hop.taskId";
 
+        // When starting from a shortcut, this contains the calling package.
+        public static final String LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE =
+                "android:transaction.hop.shortcut_calling_package";
+
         private final int mType;
 
         // Container we are performing the operation on.
@@ -999,6 +1020,9 @@
         @Nullable
         private PendingIntent mPendingIntent;
 
+        @Nullable
+        private ShortcutInfo mShortcutInfo;
+
         public static HierarchyOp createForReparent(
                 @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
             return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1058,6 +1082,17 @@
                     .build();
         }
 
+        /** Create a hierarchy op for starting a shortcut. */
+        public static HierarchyOp createForStartShortcut(@NonNull String callingPackage,
+                @NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options) {
+            final Bundle fullOptions = options == null ? new Bundle() : options;
+            fullOptions.putString(LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE, callingPackage);
+            return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_START_SHORTCUT)
+                    .setShortcutInfo(shortcutInfo)
+                    .setLaunchOptions(fullOptions)
+                    .build();
+        }
+
         /** Create a hierarchy op for setting launch adjacent flag root. */
         public static HierarchyOp createForSetLaunchAdjacentFlagRoot(IBinder container,
                 boolean clearRoot) {
@@ -1085,6 +1120,7 @@
             mActivityIntent = copy.mActivityIntent;
             mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
             mPendingIntent = copy.mPendingIntent;
+            mShortcutInfo = copy.mShortcutInfo;
         }
 
         protected HierarchyOp(Parcel in) {
@@ -1100,6 +1136,7 @@
             mActivityIntent = in.readTypedObject(Intent.CREATOR);
             mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
             mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+            mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
         }
 
         public int getType() {
@@ -1170,6 +1207,11 @@
             return mPendingIntent;
         }
 
+        @Nullable
+        public ShortcutInfo getShortcutInfo() {
+            return mShortcutInfo;
+        }
+
         @Override
         public String toString() {
             switch (mType) {
@@ -1212,6 +1254,9 @@
                 case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
                     return "{SetAdjacentTaskFragments: container=" + mContainer
                             + " adjacentContainer=" + mReparent + "}";
+                case HIERARCHY_OP_TYPE_START_SHORTCUT:
+                    return "{StartShortcut: options=" + mLaunchOptions + " info=" + mShortcutInfo
+                            + "}";
                 default:
                     return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
                             + " mToTop=" + mToTop
@@ -1234,6 +1279,7 @@
             dest.writeTypedObject(mActivityIntent, flags);
             dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
             dest.writeTypedObject(mPendingIntent, flags);
+            dest.writeTypedObject(mShortcutInfo, flags);
         }
 
         @Override
@@ -1287,6 +1333,9 @@
             @Nullable
             private PendingIntent mPendingIntent;
 
+            @Nullable
+            private ShortcutInfo mShortcutInfo;
+
             Builder(int type) {
                 mType = type;
             }
@@ -1347,6 +1396,11 @@
                 return this;
             }
 
+            Builder setShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
+                mShortcutInfo = shortcutInfo;
+                return this;
+            }
+
             HierarchyOp build() {
                 final HierarchyOp hierarchyOp = new HierarchyOp(mType);
                 hierarchyOp.mContainer = mContainer;
@@ -1364,6 +1418,7 @@
                 hierarchyOp.mActivityIntent = mActivityIntent;
                 hierarchyOp.mPendingIntent = mPendingIntent;
                 hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
+                hierarchyOp.mShortcutInfo = mShortcutInfo;
 
                 return hierarchyOp;
             }
diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java
index 4376e3e..9d4545c 100644
--- a/core/java/android/window/WindowInfosListener.java
+++ b/core/java/android/window/WindowInfosListener.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import android.graphics.Matrix;
+import android.util.Size;
 import android.view.InputWindowHandle;
 
 import libcore.util.NativeAllocationRegistry;
@@ -40,7 +42,8 @@
      * @param windowHandles Reverse Z ordered array of window information that was on screen,
      *                      where the first value is the topmost window.
      */
-    public abstract void onWindowInfosChanged(InputWindowHandle[] windowHandles);
+    public abstract void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+            DisplayInfo[] displayInfos);
 
     /**
      * Register the WindowInfosListener.
@@ -60,4 +63,34 @@
     private static native void nativeRegister(long ptr);
     private static native void nativeUnregister(long ptr);
     private static native long nativeGetFinalizer();
+
+    /**
+     * Describes information about a display that can have windows in it.
+     */
+    public static final class DisplayInfo {
+        public final int mDisplayId;
+
+        /**
+         * Logical display dimensions.
+         */
+        public final Size mLogicalSize;
+
+        /**
+         * The display transform. This takes display coordinates to logical display coordinates.
+         */
+        public final Matrix mTransform;
+
+        private DisplayInfo(int displayId, int logicalWidth, int logicalHeight, Matrix transform) {
+            mDisplayId = displayId;
+            mLogicalSize = new Size(logicalWidth, logicalHeight);
+            mTransform = transform;
+        }
+
+        @Override
+        public String toString() {
+            return "displayId=" + mDisplayId
+                    + ", mLogicalSize=" + mLogicalSize
+                    + ", mTransform=" + mTransform;
+        }
+    }
 }
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index b331a9e..4ba7ef2 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -15,7 +15,6 @@
  */
 package android.window;
 
-import static android.window.ConfigurationHelper.diffPublicWithSizeBuckets;
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 import static android.window.ConfigurationHelper.shouldUpdateResources;
@@ -222,14 +221,7 @@
                         () -> windowContext.dispatchConfigurationChanged(newConfig));
             }
 
-            // Dispatch onConfigurationChanged only if there's a significant public change to
-            // make it compatible with the original behavior.
-            final Configuration[] sizeConfigurations = context.getResources()
-                    .getSizeConfigurations();
-            final SizeConfigurationBuckets buckets = sizeConfigurations != null
-                    ? new SizeConfigurationBuckets(sizeConfigurations) : null;
-            final int diff = diffPublicWithSizeBuckets(mConfiguration, newConfig, buckets);
-
+            final int diff = mConfiguration.diffPublicOnly(newConfig);
             if (shouldReportConfigChange && diff != 0
                     && context instanceof WindowProviderService) {
                 final WindowProviderService windowProviderService = (WindowProviderService) context;
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
index 6f83bf3..d709acf 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
@@ -89,6 +89,6 @@
 
     public void readFromParcel(Parcel source) {
         mSdp = source.readString();
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
index 461f8bf..559d61b 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
@@ -147,8 +147,8 @@
     /** @hide */
     public void readFromParcel(Parcel source) {
         mUserData = source.readInt();
-        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader());
-        mStatus = source.readParcelable(StatusCode.class.getClassLoader());
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
+        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class);
+        mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class);
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
index 3242081..160f9eb 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
@@ -180,7 +180,7 @@
         mRequestId = source.readInt();
         mSipResponseCode = source.readInt();
         mReasonPhrase = source.readString();
-        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader());
+        mCmdId = source.readParcelable(OptionsCmdId.class.getClassLoader(), com.android.ims.internal.uce.options.OptionsCmdId.class);
         mRetryAfter = source.readInt();
         mReasonHeader = source.readString();
     }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
index ec8b6bf..f0ee5f3 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
@@ -105,6 +105,6 @@
     /** @hide */
     public void readFromParcel(Parcel source) {
         mContactUri = source.readString();
-        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader());
+        mCapInfo = source.readParcelable(CapInfo.class.getClassLoader(), com.android.ims.internal.uce.common.CapInfo.class);
     }
 }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
index 7e22106..8fbb000c 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
@@ -146,8 +146,8 @@
     public void readFromParcel(Parcel source) {
         mUserData = source.readInt();
         mRequestId = source.readInt();
-        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader());
-        mStatus = source.readParcelable(StatusCode.class.getClassLoader());
+        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class);
+        mStatus = source.readParcelable(StatusCode.class.getClassLoader(), com.android.ims.internal.uce.common.StatusCode.class);
     }
 
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
index 2f797b4..954c2b6 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
@@ -122,6 +122,6 @@
     public void readFromParcel(Parcel source) {
         mResUri = source.readString();
         mDisplayName = source.readString();
-        mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader());
+        mInstanceInfo = source.readParcelable(PresResInstanceInfo.class.getClassLoader(), com.android.ims.internal.uce.presence.PresResInstanceInfo.class);
     }
 }
\ No newline at end of file
diff --git a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
index e33aa13..63247db 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
@@ -236,7 +236,7 @@
         mListName = source.readString();
         mRequestId = source.readInt();
         mPresSubscriptionState = source.readParcelable(
-                                  PresSubscriptionState.class.getClassLoader());
+                                  PresSubscriptionState.class.getClassLoader(), com.android.ims.internal.uce.presence.PresSubscriptionState.class);
         mSubscriptionExpireTime = source.readInt();
         mSubscriptionTerminatedReason = source.readString();
     }
diff --git a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
index 5e394ef..8097a37 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
@@ -185,7 +185,7 @@
         mRequestId = source.readInt();
         mSipResponseCode = source.readInt();
         mReasonPhrase = source.readString();
-        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader());
+        mCmdId = source.readParcelable(PresCmdId.class.getClassLoader(), com.android.ims.internal.uce.presence.PresCmdId.class);
         mRetryAfter = source.readInt();
         mReasonHeader = source.readString();
     }
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 359c382..bc3c2f5 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -171,9 +171,6 @@
     private AppPredictor mWorkAppPredictor;
     private boolean mShouldDisplayLandscape;
 
-    private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
-    private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
-
     @UnsupportedAppUsage
     public ChooserActivity() {
     }
@@ -294,6 +291,7 @@
 
     private int mCurrAvailableWidth = 0;
     private int mLastNumberOfChildren = -1;
+    private int mMaxTargetsPerRow = 1;
 
     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
 
@@ -681,8 +679,9 @@
             mCallerChooserTargets = targets;
         }
 
-        mShouldDisplayLandscape = shouldDisplayLandscape(
-                getResources().getConfiguration().orientation);
+        mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
+        mShouldDisplayLandscape =
+                shouldDisplayLandscape(getResources().getConfiguration().orientation);
         setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
                 null, false);
@@ -915,7 +914,7 @@
                 adapter,
                 getPersonalProfileUserHandle(),
                 /* workProfileUserHandle= */ null,
-                isSendAction(getTargetIntent()), getMaxTargetsPerRow());
+                isSendAction(getTargetIntent()), mMaxTargetsPerRow);
     }
 
     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
@@ -944,7 +943,7 @@
                 selectedProfile,
                 getPersonalProfileUserHandle(),
                 getWorkProfileUserHandle(),
-                isSendAction(getTargetIntent()), getMaxTargetsPerRow());
+                isSendAction(getTargetIntent()), mMaxTargetsPerRow);
     }
 
     private int findSelectedProfile() {
@@ -1112,6 +1111,7 @@
         }
 
         mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
+        mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
         adjustPreviewWidth(newConfig.orientation, null);
         updateStickyContentPreview();
     }
@@ -2560,7 +2560,7 @@
                 // and b/150936654
                 recyclerView.setAdapter(gridAdapter);
                 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
-                        getMaxTargetsPerRow());
+                        mMaxTargetsPerRow);
             }
 
             UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
@@ -2724,7 +2724,7 @@
 
     @Override // ChooserListCommunicator
     public int getMaxRankedTargets() {
-        return getMaxTargetsPerRow();
+        return mMaxTargetsPerRow;
     }
 
     @Override // ChooserListCommunicator
@@ -2736,7 +2736,7 @@
     }
 
     @Override
-    public void onListRebuilt(ResolverListAdapter listAdapter) {
+    public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
         setupScrollListener();
         maybeSetupGlobalLayoutListener();
 
@@ -2756,15 +2756,20 @@
             chooserListAdapter.updateAlphabeticalList();
         }
 
+        if (rebuildComplete) {
+            getChooserActivityLogger().logSharesheetAppLoadComplete();
+            maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
+        }
+    }
+
+    private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
         // don't support direct share on low ram devices
         if (ActivityManager.isLowRamDeviceStatic()) {
-            getChooserActivityLogger().logSharesheetAppLoadComplete();
             return;
         }
 
         // no need to query direct share for work profile when its locked or disabled
         if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
-            getChooserActivityLogger().logSharesheetAppLoadComplete();
             return;
         }
 
@@ -2775,8 +2780,6 @@
 
             queryDirectShareTargets(chooserListAdapter, false);
         }
-
-        getChooserActivityLogger().logSharesheetAppLoadComplete();
     }
 
     @VisibleForTesting
@@ -3072,13 +3075,6 @@
         }
     }
 
-    int getMaxTargetsPerRow() {
-        int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
-        if (mShouldDisplayLandscape) {
-            maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
-        }
-        return maxTargets;
-    }
     /**
      * Adapter for all types of items and targets in ShareSheet.
      * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
@@ -3146,7 +3142,11 @@
                 return false;
             }
 
-            int newWidth = width / getMaxTargetsPerRow();
+            // Limit width to the maximum width of the chooser activity
+            int maxWidth = getResources().getDimensionPixelSize(R.dimen.chooser_width);
+            width = Math.min(maxWidth, width);
+
+            int newWidth = width / mMaxTargetsPerRow;
             if (newWidth != mChooserTargetWidth) {
                 mChooserTargetWidth = newWidth;
                 return true;
@@ -3181,7 +3181,7 @@
                             + getAzLabelRowCount()
                             + Math.ceil(
                             (float) mChooserListAdapter.getAlphaTargetCount()
-                                    / getMaxTargetsPerRow())
+                                    / mMaxTargetsPerRow)
             );
         }
 
@@ -3221,7 +3221,7 @@
         public int getCallerAndRankedTargetRowCount() {
             return (int) Math.ceil(
                     ((float) mChooserListAdapter.getCallerTargetCount()
-                            + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
+                            + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow);
         }
 
         // There can be at most one row in the listview, that is internally
@@ -3420,7 +3420,7 @@
                 parentGroup.addView(row2);
 
                 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
-                        Lists.newArrayList(row1, row2), getMaxTargetsPerRow(), viewType,
+                        Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType,
                         mChooserMultiProfilePagerAdapter::getActiveListAdapter);
                 loadViewsIntoGroup(mDirectShareViewHolder);
 
@@ -3429,7 +3429,7 @@
                 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
                         false);
                 ItemGroupViewHolder holder =
-                        new SingleRowViewHolder(row, getMaxTargetsPerRow(), viewType);
+                        new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType);
                 loadViewsIntoGroup(holder);
 
                 return holder;
@@ -3521,7 +3521,7 @@
             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
             final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets());
             if (position < serviceRows) {
-                return position * getMaxTargetsPerRow();
+                return position * mMaxTargetsPerRow;
             }
 
             position -= serviceRows;
@@ -3530,7 +3530,7 @@
                                                  + mChooserListAdapter.getRankedTargetCount();
             final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
             if (position < callerAndRankedRows) {
-                return serviceCount + position * getMaxTargetsPerRow();
+                return serviceCount + position * mMaxTargetsPerRow;
             }
 
             position -= getAzLabelRowCount() + callerAndRankedRows;
@@ -3543,7 +3543,7 @@
             if (mDirectShareViewHolder != null && canExpandDirectShare) {
                 mDirectShareViewHolder.handleScroll(
                         mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
-                        getMaxTargetsPerRow());
+                        mMaxTargetsPerRow);
             }
         }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index fd8637a..b273f6d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1159,11 +1159,11 @@
         if (doPostProcessing) {
             maybeCreateHeader(listAdapter);
             resetButtonBar();
-            onListRebuilt(listAdapter);
+            onListRebuilt(listAdapter, rebuildCompleted);
         }
     }
 
-    protected void onListRebuilt(ResolverListAdapter listAdapter) {
+    protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
         final ItemClickListener listener = new ItemClickListener();
         setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
         if (shouldShowTabs() && isIntentPicker()) {
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
index 4f2e7db..289daee 100644
--- a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -237,11 +237,11 @@
     private DisplayResolveInfo(Parcel in) {
         mDisplayLabel = in.readCharSequence();
         mExtendedInfo = in.readCharSequence();
-        mResolvedIntent = in.readParcelable(null /* ClassLoader */);
+        mResolvedIntent = in.readParcelable(null /* ClassLoader */, android.content.Intent.class);
         mSourceIntents.addAll(
                 Arrays.asList((Intent[]) in.readParcelableArray(null /* ClassLoader */)));
         mIsSuspended = in.readBoolean();
         mPinned = in.readBoolean();
-        mResolveInfo = in.readParcelable(null /* ClassLoader */);
+        mResolveInfo = in.readParcelable(null /* ClassLoader */, android.content.pm.ResolveInfo.class);
     }
 }
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index 410e68b..29bb311 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -89,7 +89,7 @@
                 // contribution to mTextView's nested batch edit count is zero.
                 mTextView.endBatchEdit();
                 mBatchEditNesting--;
-                return true;
+                return mBatchEditNesting > 0;
             }
         }
         return false;
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index 6c2b330..662ed6b 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -25,6 +25,7 @@
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
+import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -69,6 +70,82 @@
     private static final String TAG = "RemoteInputConnectionImpl";
     private static final boolean DEBUG = false;
 
+    /**
+     * An upper limit of calling {@link InputConnection#endBatchEdit()}.
+     *
+     * <p>This is a safeguard against broken {@link InputConnection#endBatchEdit()} implementations,
+     * which are real as we've seen in Bug 208941904.  If the retry count reaches to the number
+     * defined here, we fall back into {@link InputMethodManager#restartInput(View)} as a
+     * workaround.</p>
+     */
+    private static final int MAX_END_BATCH_EDIT_RETRY = 16;
+
+    /**
+     * A lightweight per-process type cache to remember classes that never returns {@code false}
+     * from {@link InputConnection#endBatchEdit()}.  The implementation is optimized for simplicity
+     * and speed with accepting false-negatives in {@link #contains(Class)}.
+     */
+    private static final class KnownAlwaysTrueEndBatchEditCache {
+        @Nullable
+        private static volatile Class<?> sElement;
+        @Nullable
+        private static volatile Class<?>[] sArray;
+
+        /**
+         * Query if the specified {@link InputConnection} implementation is known to be broken, with
+         * allowing false-negative results.
+         *
+         * @param klass An implementation class of {@link InputConnection} to be tested.
+         * @return {@code true} if the specified type was passed to {@link #add(Class)}.
+         *         Note that there is a chance that you still receive {@code false} even if you
+         *         called {@link #add(Class)} (false-negative).
+         */
+        @AnyThread
+        static boolean contains(@NonNull Class<? extends InputConnection> klass) {
+            if (klass == sElement) {
+                return true;
+            }
+            final Class<?>[] array = sArray;
+            if (array == null) {
+                return false;
+            }
+            for (Class<?> item : array) {
+                if (item == klass) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Try to remember the specified {@link InputConnection} implementation as a known bad.
+         *
+         * <p>There is a chance that calling this method can accidentally overwrite existing
+         * cache entries. See the document of {@link #contains(Class)} for details.</p>
+         *
+         * @param klass The implementation class of {@link InputConnection} to be remembered.
+         */
+        @AnyThread
+        static void add(@NonNull Class<? extends InputConnection> klass) {
+            if (sElement == null) {
+                // OK to accidentally overwrite an existing element that was set by another thread.
+                sElement = klass;
+                return;
+            }
+
+            final Class<?>[] array = sArray;
+            final int arraySize = array != null ? array.length : 0;
+            final Class<?>[] newArray = new Class<?>[arraySize + 1];
+            for (int i = 0; i < arraySize; ++i) {
+                newArray[i] = array[i];
+            }
+            newArray[arraySize] = klass;
+
+            // OK to accidentally overwrite an existing array that was set by another thread.
+            sArray = newArray;
+        }
+    }
+
     @Retention(SOURCE)
     private @interface Dispatching {
         boolean cancellable();
@@ -155,32 +232,63 @@
             mH.post(() -> {
                 try {
                     if (isFinished()) {
+                        // This is a stale request, which can happen.  No need to show a warning
+                        // because this situation itself is not an error.
                         return;
                     }
                     final InputConnection ic = getInputConnection();
                     if (ic == null) {
+                        // This is a stale request, which can happen.  No need to show a warning
+                        // because this situation itself is not an error.
+                        return;
+                    }
+                    final View view = getServedView();
+                    if (view == null) {
+                        // This is a stale request, which can happen.  No need to show a warning
+                        // because this situation itself is not an error.
                         return;
                     }
 
-                    // Clean up composing text and batch edit.
-                    ic.finishComposingText();
-                    // Also clean up batch edit.
-                    while (true) {
-                        if (!ic.endBatchEdit()) {
-                            break;
+                    final Class<? extends InputConnection> icClass = ic.getClass();
+
+                    boolean alwaysTrueEndBatchEditDetected =
+                            KnownAlwaysTrueEndBatchEditCache.contains(icClass);
+
+                    if (!alwaysTrueEndBatchEditDetected) {
+                        // Clean up composing text and batch edit.
+                        final boolean supportsBatchEdit = ic.beginBatchEdit();
+                        ic.finishComposingText();
+                        if (supportsBatchEdit) {
+                            // Also clean up batch edit.
+                            int retryCount = 0;
+                            while (true) {
+                                if (!ic.endBatchEdit()) {
+                                    break;
+                                }
+                                ++retryCount;
+                                if (retryCount > MAX_END_BATCH_EDIT_RETRY) {
+                                    Log.e(TAG, icClass.getTypeName() + "#endBatchEdit() still"
+                                            + " returns true even after retrying "
+                                            + MAX_END_BATCH_EDIT_RETRY + " times.  Falling back to"
+                                            + " InputMethodManager#restartInput(View)");
+                                    alwaysTrueEndBatchEditDetected = true;
+                                    KnownAlwaysTrueEndBatchEditCache.add(icClass);
+                                    break;
+                                }
+                            }
                         }
                     }
 
-                    final TextSnapshot textSnapshot = ic.takeSnapshot();
-                    if (textSnapshot == null) {
-                        final View view = getServedView();
-                        if (view == null) {
+                    if (!alwaysTrueEndBatchEditDetected) {
+                        final TextSnapshot textSnapshot = ic.takeSnapshot();
+                        if (textSnapshot != null) {
+                            mParentInputMethodManager.doInvalidateInput(this, textSnapshot,
+                                    nextSessionId);
                             return;
                         }
-                        mParentInputMethodManager.restartInput(view);
-                        return;
                     }
-                    mParentInputMethodManager.doInvalidateInput(this, textSnapshot, nextSessionId);
+
+                    mParentInputMethodManager.restartInput(view);
                 } finally {
                     mHasPendingInvalidation.set(false);
                 }
diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java
index 43984b5..b3bc93a 100644
--- a/core/java/com/android/internal/net/LegacyVpnInfo.java
+++ b/core/java/com/android/internal/net/LegacyVpnInfo.java
@@ -69,7 +69,7 @@
             LegacyVpnInfo info = new LegacyVpnInfo();
             info.key = in.readString();
             info.state = in.readInt();
-            info.intent = in.readParcelable(null);
+            info.intent = in.readParcelable(null, android.app.PendingIntent.class);
             return info;
         }
 
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index 2e7629a..2a203ac 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -192,7 +192,7 @@
             config.searchDomains = in.createStringArrayList();
             config.allowedApplications = in.createStringArrayList();
             config.disallowedApplications = in.createStringArrayList();
-            config.configureIntent = in.readParcelable(null);
+            config.configureIntent = in.readParcelable(null, android.app.PendingIntent.class);
             config.startTime = in.readLong();
             config.legacy = in.readInt() != 0;
             config.blocking = in.readInt() != 0;
@@ -201,7 +201,7 @@
             config.allowIPv6 = in.readInt() != 0;
             config.isMetered = in.readInt() != 0;
             config.underlyingNetworks = in.createTypedArray(Network.CREATOR);
-            config.proxyInfo = in.readParcelable(null);
+            config.proxyInfo = in.readParcelable(null, android.net.ProxyInfo.class);
             return config;
         }
 
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 5f84b5a..d0a8c32 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -175,9 +175,9 @@
         ipsecCaCert = in.readString();
         ipsecServerCert = in.readString();
         saveLogin = in.readInt() != 0;
-        proxy = in.readParcelable(null);
+        proxy = in.readParcelable(null, android.net.ProxyInfo.class);
         mAllowedAlgorithms = new ArrayList<>();
-        in.readList(mAllowedAlgorithms, null);
+        in.readList(mAllowedAlgorithms, null, java.lang.String.class);
         isBypassable = in.readBoolean();
         isMetered = in.readBoolean();
         maxMtu = in.readInt();
diff --git a/core/java/com/android/internal/os/AppFuseMount.java b/core/java/com/android/internal/os/AppFuseMount.java
index 04d7211..5404fea 100644
--- a/core/java/com/android/internal/os/AppFuseMount.java
+++ b/core/java/com/android/internal/os/AppFuseMount.java
@@ -57,7 +57,7 @@
             new Parcelable.Creator<AppFuseMount>() {
         @Override
         public AppFuseMount createFromParcel(Parcel in) {
-            return new AppFuseMount(in.readInt(), in.readParcelable(null));
+            return new AppFuseMount(in.readInt(), in.readParcelable(null, android.os.ParcelFileDescriptor.class));
         }
 
         @Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aba43d8..209c64a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -18,6 +18,7 @@
 
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
 import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
 import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
 
@@ -160,7 +161,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 204;
+    static final int VERSION = 205;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -239,34 +240,19 @@
 
     private static final int[] SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS = {
             MeasuredEnergyStats.POWER_BUCKET_CPU,
+            MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
     };
 
+    // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
+    // Uid.PROCESS_STATE_NONEXISTENT, which is outside the range of legitimate proc states.
+    private static final int PROC_STATE_TIME_COUNTER_STATE_COUNT = NUM_PROCESS_STATE + 1;
+
     @GuardedBy("this")
     public boolean mPerProcStateCpuTimesAvailable = true;
 
-    /**
-     * When per process state cpu times tracking is off, cpu times in KernelSingleUidTimeReader are
-     * not updated. So, when the setting is turned on later, we would end up with huge cpu time
-     * deltas. This flag tracks the case where tracking is turned on from off so that we won't
-     * end up attributing the huge deltas to wrong buckets.
-     */
-    @GuardedBy("this")
-    private boolean mIsPerProcessStateCpuDataStale;
-
-    /**
-     * Uids for which per-procstate cpu times need to be updated.
-     *
-     * Contains uid -> procState mappings.
-     */
-    @GuardedBy("this")
-    @VisibleForTesting
-    protected final SparseIntArray mPendingUids = new SparseIntArray();
-
     @GuardedBy("this")
     private long mNumSingleUidCpuTimeReads;
     @GuardedBy("this")
-    private long mNumBatchedSingleUidCpuTimeReads;
-    @GuardedBy("this")
     private long mCpuTimeReadsTrackingStartTimeMs = SystemClock.uptimeMillis();
     @GuardedBy("this")
     private int mNumUidsRemoved;
@@ -443,88 +429,42 @@
     }
 
     /**
-     * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+     * Update per-freq cpu times for the supplied UID.
      */
-    public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
-        final SparseIntArray uidStates;
-        synchronized (BatteryStatsImpl.this) {
-            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
-                return;
-            }
-            if(!initKernelSingleUidTimeReaderLocked()) {
-                return;
-            }
-            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
-            // compute deltas since it might result in mis-attributing cpu times to wrong states.
-            if (mIsPerProcessStateCpuDataStale) {
-                mPendingUids.clear();
-                return;
-            }
-
-            if (mPendingUids.size() == 0) {
-                return;
-            }
-            uidStates = mPendingUids.clone();
-            mPendingUids.clear();
+    @GuardedBy("this")
+    @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
+    @VisibleForTesting
+    public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+        if (!initKernelSingleUidTimeReaderLocked()) {
+            return;
         }
-        final long timestampMs = mClock.elapsedRealtime();
-        LongArrayMultiStateCounter.LongArrayContainer deltaContainer = null;
-        for (int i = uidStates.size() - 1; i >= 0; --i) {
-            final int uid = uidStates.keyAt(i);
-            final int procState = uidStates.valueAt(i);
-            final int[] isolatedUids;
-            final LongArrayMultiStateCounter[] isolatedUidTimeInFreqCounters;
-            final Uid u;
-            synchronized (BatteryStatsImpl.this) {
-                // It's possible that uid no longer exists and any internal references have
-                // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
-                // creating an UidStats object if it doesn't already exist.
-                u = getAvailableUidStatsLocked(uid);
-                if (u == null) {
-                    continue;
-                }
-                if (u.mChildUids == null) {
-                    isolatedUids = null;
-                    isolatedUidTimeInFreqCounters = null;
-                } else {
-                    int childUidCount = u.mChildUids.size();
-                    isolatedUids = new int[childUidCount];
-                    isolatedUidTimeInFreqCounters = new LongArrayMultiStateCounter[childUidCount];
-                    for (int j = childUidCount - 1; j >= 0; --j) {
-                        isolatedUids[j] = u.mChildUids.keyAt(j);
-                        isolatedUidTimeInFreqCounters[j] =
-                                u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
-                        if (deltaContainer == null && isolatedUidTimeInFreqCounters[j] != null) {
-                            deltaContainer = getCpuTimeInFreqContainer();
-                        }
-                    }
+
+        final Uid u = getUidStatsLocked(uid);
+
+        mNumSingleUidCpuTimeReads++;
+
+        LongArrayMultiStateCounter onBatteryCounter =
+                u.getProcStateTimeCounter().getCounter();
+        LongArrayMultiStateCounter onBatteryScreenOffCounter =
+                u.getProcStateScreenOffTimeCounter().getCounter();
+
+        mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
+        mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+
+        if (u.mChildUids != null) {
+            LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+                    getCpuTimeInFreqContainer();
+            int childUidCount = u.mChildUids.size();
+            for (int j = childUidCount - 1; j >= 0; --j) {
+                LongArrayMultiStateCounter cpuTimeInFreqCounter =
+                        u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+                if (cpuTimeInFreqCounter != null) {
+                    mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
+                            cpuTimeInFreqCounter, timestampMs, deltaContainer);
+                    onBatteryCounter.addCounts(deltaContainer);
+                    onBatteryScreenOffCounter.addCounts(deltaContainer);
                 }
             }
-
-            LongArrayMultiStateCounter onBatteryCounter =
-                    u.getProcStateTimeCounter().getCounter();
-            LongArrayMultiStateCounter onBatteryScreenOffCounter =
-                    u.getProcStateScreenOffTimeCounter().getCounter();
-
-            onBatteryCounter.setState(procState, timestampMs);
-            mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-
-            onBatteryScreenOffCounter.setState(procState, timestampMs);
-            mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
-
-            if (isolatedUids != null) {
-                for (int j = isolatedUids.length - 1; j >= 0; --j) {
-                    if (isolatedUidTimeInFreqCounters[j] != null) {
-                        mKernelSingleUidTimeReader.addDelta(isolatedUids[j],
-                                isolatedUidTimeInFreqCounters[j], timestampMs, deltaContainer);
-                        onBatteryCounter.addCounts(deltaContainer);
-                        onBatteryScreenOffCounter.addCounts(deltaContainer);
-                    }
-                }
-            }
-
-            onBatteryCounter.setState(u.mProcessState, timestampMs);
-            onBatteryScreenOffCounter.setState(u.mProcessState, timestampMs);
         }
     }
 
@@ -542,24 +482,17 @@
         }
     }
 
-    public void copyFromAllUidsCpuTimes() {
-        synchronized (BatteryStatsImpl.this) {
-            copyFromAllUidsCpuTimes(
-                    mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning());
-        }
-    }
-
     /**
      * When the battery/screen state changes, we don't attribute the cpu times to any process
-     * but we still need to snapshots of all uids to get correct deltas later on. Since we
-     * already read this data for updating per-freq cpu times, we can use the same data for
-     * per-procstate cpu times.
+     * but we still need to take snapshots of all uids to get correct deltas later on.
      */
-    public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
+    @SuppressWarnings("GuardedBy")    // errorprone false positive on getProcStateTimeCounter
+    public void updateCpuTimesForAllUids() {
         synchronized (BatteryStatsImpl.this) {
-            if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+            if (!trackPerProcStateCpuTimes()) {
                 return;
             }
+
             if(!initKernelSingleUidTimeReaderLocked()) {
                 return;
             }
@@ -567,14 +500,6 @@
             // TODO(b/197162116): just get a list of UIDs
             final SparseArray<long[]> allUidCpuFreqTimesMs =
                     mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
-            // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
-            // compute deltas since it might result in mis-attributing cpu times to wrong states.
-            if (mIsPerProcessStateCpuDataStale) {
-                mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
-                mIsPerProcessStateCpuDataStale = false;
-                mPendingUids.clear();
-                return;
-            }
             for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
                 final int uid = allUidCpuFreqTimesMs.keyAt(i);
                 final int parentUid = mapUid(uid);
@@ -583,16 +508,8 @@
                     continue;
                 }
 
-                final int procState;
-                final int idx = mPendingUids.indexOfKey(uid);
-                if (idx >= 0) {
-                    procState = mPendingUids.valueAt(idx);
-                    mPendingUids.removeAt(idx);
-                } else {
-                    procState = u.mProcessState;
-                }
-
-                if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                final int procState = u.mProcessState;
+                if (procState == Uid.PROCESS_STATE_NONEXISTENT) {
                     continue;
                 }
 
@@ -602,27 +519,19 @@
                 final LongArrayMultiStateCounter onBatteryScreenOffCounter =
                         u.getProcStateScreenOffTimeCounter().getCounter();
 
-                onBatteryCounter.setState(procState, timestampMs);
                 if (uid == parentUid) {
                     mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-                }
-
-                onBatteryScreenOffCounter.setState(procState, timestampMs);
-                if (uid == parentUid) {
                     mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter,
                             timestampMs);
-                }
-
-                if (u.mChildUids != null) {
-                    for (int j = u.mChildUids.size() - 1; j >= 0; --j) {
-                        final LongArrayMultiStateCounter counter =
-                                u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+                } else {
+                    Uid.ChildUid childUid = u.getChildUid(uid);
+                    if (childUid != null) {
+                        final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
                         if (counter != null) {
-                            final int isolatedUid = u.mChildUids.keyAt(j);
                             final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
                                     getCpuTimeInFreqContainer();
-                            mKernelSingleUidTimeReader.addDelta(isolatedUid,
-                                    counter, timestampMs, deltaContainer);
+                            mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+                                    deltaContainer);
                             onBatteryCounter.addCounts(deltaContainer);
                             onBatteryScreenOffCounter.addCounts(deltaContainer);
                         }
@@ -674,6 +583,8 @@
         int UPDATE_ALL =
                 UPDATE_CPU | UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT | UPDATE_RPM | UPDATE_DISPLAY;
 
+        int UPDATE_ON_PROC_STATE_CHANGE = UPDATE_WIFI | UPDATE_RADIO | UPDATE_BT;
+
         @IntDef(flag = true, prefix = "UPDATE_", value = {
                 UPDATE_CPU,
                 UPDATE_WIFI,
@@ -689,9 +600,6 @@
 
         Future<?> scheduleSync(String reason, int flags);
         Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
-        Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff,
-                long delayMillis);
-        Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
         Future<?> scheduleCpuSyncDueToSettingChange();
         /**
          * Schedule a sync because of a screen state change.
@@ -703,6 +611,8 @@
         Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis);
         /** Schedule removal of UIDs corresponding to a removed user */
         Future<?> scheduleCleanupDueToRemovedUser(int userId);
+        /** Schedule a sync because of a process state change */
+        Future<?> scheduleSyncDueToProcessStateChange(long delayMillis);
     }
 
     public Handler mHandler;
@@ -1798,7 +1708,6 @@
         }
     }
 
-
     private static class TimeMultiStateCounter implements TimeBaseObs {
         private final TimeBase mTimeBase;
         private final LongMultiStateCounter mCounter;
@@ -1831,18 +1740,10 @@
             mCounter.setEnabled(false, elapsedRealtimeUs / 1000);
         }
 
-        public LongMultiStateCounter getCounter() {
-            return mCounter;
-        }
-
         public int getStateCount() {
             return mCounter.getStateCount();
         }
 
-        public void setTrackingEnabled(boolean enabled, long timestampMs) {
-            mCounter.setEnabled(enabled && mTimeBase.isRunning(), timestampMs);
-        }
-
         private void setState(@BatteryConsumer.ProcessState int processState,
                 long elapsedRealtimeMs) {
             mCounter.setState(processState, elapsedRealtimeMs);
@@ -1852,8 +1753,8 @@
             return mCounter.updateValue(value, timestampMs);
         }
 
-        public void addCount(long delta) {
-            mCounter.addCount(delta);
+        private void increment(long increment, long timestampMs) {
+            mCounter.incrementValue(increment, timestampMs);
         }
 
         /**
@@ -1863,6 +1764,10 @@
             return mCounter.getCount(procState);
         }
 
+        public long getTotalCountLocked() {
+            return mCounter.getTotalCount();
+        }
+
         public void logState(Printer pw, String prefix) {
             pw.println(prefix + "mCounter=" + mCounter);
         }
@@ -8073,7 +7978,7 @@
         Counter mBluetoothScanResultCounter;
         Counter mBluetoothScanResultBgCounter;
 
-        int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+        int mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
         StopwatchTimer[] mProcessStateTimer;
 
         boolean mInForegroundService = false;
@@ -8084,7 +7989,7 @@
 
         LongSamplingCounter[] mNetworkByteActivityCounters;
         LongSamplingCounter[] mNetworkPacketActivityCounters;
-        LongSamplingCounter mMobileRadioActiveTime;
+        TimeMultiStateCounter mMobileRadioActiveTime;
         LongSamplingCounter mMobileRadioActiveCount;
 
         /**
@@ -8294,6 +8199,7 @@
             final int batteryConsumerProcessState =
                     mapUidProcessStateToBatteryConsumerProcessState(procState);
             getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs);
+            getMobileRadioActiveTimeCounter().setState(batteryConsumerProcessState, elapsedTimeMs);
             final MeasuredEnergyStats energyStats =
                     getOrCreateMeasuredEnergyStatsIfSupportedLocked();
             if (energyStats != null) {
@@ -8415,6 +8321,11 @@
             mChildUids.remove(idx);
         }
 
+        @GuardedBy("mBsi")
+        ChildUid getChildUid(int childUid) {
+            return mChildUids == null ? null : mChildUids.get(childUid);
+        }
+
         private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
             if (cpuTimesMs == null) {
                 return null;
@@ -8432,6 +8343,7 @@
             return null;
         }
 
+        @GuardedBy("mBsi")
         private void ensureMultiStateCounters() {
             if (mProcStateTimeMs != null) {
                 return;
@@ -8440,31 +8352,26 @@
             final long timestampMs = mBsi.mClock.elapsedRealtime();
             mProcStateTimeMs =
                     new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
-                            NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+                            PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+                            timestampMs);
             mProcStateScreenOffTimeMs =
                     new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
-                            NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+                            PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+                            timestampMs);
         }
 
+        @GuardedBy("mBsi")
         private TimeInFreqMultiStateCounter getProcStateTimeCounter() {
             ensureMultiStateCounters();
             return mProcStateTimeMs;
         }
 
+        @GuardedBy("mBsi")
         private TimeInFreqMultiStateCounter getProcStateScreenOffTimeCounter() {
             ensureMultiStateCounters();
             return mProcStateScreenOffTimeMs;
         }
 
-        private void setProcStateTimesTrackingEnabled(boolean enabled, long timestampMs) {
-            if (mProcStateTimeMs != null) {
-                mProcStateTimeMs.setTrackingEnabled(enabled, timestampMs);
-            }
-            if (mProcStateScreenOffTimeMs != null) {
-                mProcStateScreenOffTimeMs.setTrackingEnabled(enabled, timestampMs);
-            }
-        }
-
         @Override
         public Timer getAggregatedPartialWakelockTimer() {
             return mAggregatedPartialWakelockTimer;
@@ -8692,11 +8599,10 @@
         /** Adds the given charge to the given standard power bucket for this uid. */
         @GuardedBy("mBsi")
         private void addChargeToStandardBucketLocked(long chargeDeltaUC,
-                @StandardPowerBucket int powerBucket) {
+                @StandardPowerBucket int powerBucket, long timestampMs) {
             final MeasuredEnergyStats measuredEnergyStats =
                     getOrCreateMeasuredEnergyStatsLocked();
-            measuredEnergyStats.updateStandardBucket(powerBucket, chargeDeltaUC,
-                    mBsi.mClock.elapsedRealtime());
+            measuredEnergyStats.updateStandardBucket(powerBucket, chargeDeltaUC, timestampMs);
         }
 
         /** Adds the given charge to the given custom power bucket for this uid. */
@@ -8774,6 +8680,7 @@
                     processState);
         }
 
+        @GuardedBy("mBsi")
         @Override
         public long getGnssMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
@@ -8787,6 +8694,13 @@
 
         @GuardedBy("mBsi")
         @Override
+        public long getMobileRadioMeasuredBatteryConsumptionUC(int processState) {
+            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
+                    processState);
+        }
+
+        @GuardedBy("mBsi")
+        @Override
         public long getScreenOnMeasuredBatteryConsumptionUC() {
             return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
         }
@@ -9325,9 +9239,7 @@
         }
 
         void noteNetworkActivityLocked(int type, long deltaBytes, long deltaPackets) {
-            if (mNetworkByteActivityCounters == null) {
-                initNetworkActivityLocked();
-            }
+            ensureNetworkActivityLocked();
             if (type >= 0 && type < NUM_NETWORK_ACTIVITY_TYPES) {
                 mNetworkByteActivityCounters[type].addCountLocked(deltaBytes);
                 mNetworkPacketActivityCounters[type].addCountLocked(deltaPackets);
@@ -9337,14 +9249,25 @@
             }
         }
 
-        void noteMobileRadioActiveTimeLocked(long batteryUptime) {
-            if (mNetworkByteActivityCounters == null) {
-                initNetworkActivityLocked();
-            }
-            mMobileRadioActiveTime.addCountLocked(batteryUptime);
+        void noteMobileRadioActiveTimeLocked(long batteryUptimeDeltaUs, long elapsedTimeMs) {
+            ensureNetworkActivityLocked();
+            getMobileRadioActiveTimeCounter().increment(batteryUptimeDeltaUs, elapsedTimeMs);
             mMobileRadioActiveCount.addCountLocked(1);
         }
 
+        private TimeMultiStateCounter getMobileRadioActiveTimeCounter() {
+            if (mMobileRadioActiveTime == null) {
+                final long timestampMs = mBsi.mClock.elapsedRealtime();
+                mMobileRadioActiveTime = new TimeMultiStateCounter(
+                        mBsi.mOnBatteryTimeBase, BatteryConsumer.PROCESS_STATE_COUNT, timestampMs);
+                mMobileRadioActiveTime.setState(
+                        mapUidProcessStateToBatteryConsumerProcessState(mProcessState),
+                        timestampMs);
+                mMobileRadioActiveTime.update(0, timestampMs);
+            }
+            return mMobileRadioActiveTime;
+        }
+
         @Override
         public boolean hasNetworkActivity() {
             return mNetworkByteActivityCounters != null;
@@ -9372,8 +9295,20 @@
 
         @Override
         public long getMobileRadioActiveTime(int which) {
-            return mMobileRadioActiveTime != null
-                    ? mMobileRadioActiveTime.getCountLocked(which) : 0;
+            return getMobileRadioActiveTimeInProcessState(BatteryConsumer.PROCESS_STATE_ANY);
+        }
+
+        @Override
+        public long getMobileRadioActiveTimeInProcessState(
+                @BatteryConsumer.ProcessState int processState) {
+            if (mMobileRadioActiveTime == null) {
+                return 0;
+            }
+            if (processState == BatteryConsumer.PROCESS_STATE_ANY) {
+                return mMobileRadioActiveTime.getTotalCountLocked();
+            } else {
+                return mMobileRadioActiveTime.getCountLocked(processState);
+            }
         }
 
         @Override
@@ -9485,18 +9420,17 @@
             }
         }
 
-        void initNetworkActivityLocked() {
-            detachIfNotNull(mNetworkByteActivityCounters);
+        void ensureNetworkActivityLocked() {
+            if (mNetworkByteActivityCounters != null) {
+                return;
+            }
+
             mNetworkByteActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
-            detachIfNotNull(mNetworkPacketActivityCounters);
             mNetworkPacketActivityCounters = new LongSamplingCounter[NUM_NETWORK_ACTIVITY_TYPES];
             for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
                 mNetworkByteActivityCounters[i] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
                 mNetworkPacketActivityCounters[i] = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
             }
-            detachIfNotNull(mMobileRadioActiveTime);
-            mMobileRadioActiveTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
-            detachIfNotNull(mMobileRadioActiveCount);
             mMobileRadioActiveCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase);
         }
 
@@ -9553,7 +9487,7 @@
                 for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                     active |= !resetIfNotNull(mProcessStateTimer[i], false, realtimeUs);
                 }
-                active |= (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT);
+                active |= (mProcessState != Uid.PROCESS_STATE_NONEXISTENT);
             }
             if (mVibratorOnTimer != null) {
                 if (mVibratorOnTimer.reset(false, realtimeUs)) {
@@ -9993,7 +9927,12 @@
                     mNetworkByteActivityCounters[i].writeToParcel(out);
                     mNetworkPacketActivityCounters[i].writeToParcel(out);
                 }
-                mMobileRadioActiveTime.writeToParcel(out);
+                if (mMobileRadioActiveTime != null) {
+                    out.writeBoolean(true);
+                    mMobileRadioActiveTime.writeToParcel(out);
+                } else {
+                    out.writeBoolean(false);
+                }
                 mMobileRadioActiveCount.writeToParcel(out);
             } else {
                 out.writeInt(0);
@@ -10093,6 +10032,7 @@
 
         @GuardedBy("mBsi")
         void readFromParcelLocked(TimeBase timeBase, TimeBase screenOffTimeBase, Parcel in) {
+            final long timestampMs = mBsi.mClock.elapsedRealtime();
             mOnBatteryBackgroundTimeBase.readFromParcel(in);
             mOnBatteryScreenOffBackgroundTimeBase.readFromParcel(in);
 
@@ -10270,7 +10210,7 @@
             } else {
                 mBluetoothScanResultBgCounter = null;
             }
-            mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+            mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
             for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                 if (in.readInt() != 0) {
                     makeProcessState(i, in);
@@ -10302,7 +10242,14 @@
                     mNetworkPacketActivityCounters[i]
                             = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
                 }
-                mMobileRadioActiveTime = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
+                if (in.readBoolean()) {
+                    final TimeMultiStateCounter counter =
+                            new TimeMultiStateCounter(mBsi.mOnBatteryTimeBase, in, timestampMs);
+                    if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) {
+                        mMobileRadioActiveTime = counter;
+                    }
+                }
+
                 mMobileRadioActiveCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
             } else {
                 mNetworkByteActivityCounters = null;
@@ -10344,7 +10291,6 @@
             mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
                     in, mBsi.mOnBatteryScreenOffTimeBase);
 
-            final long timestampMs = mBsi.mClock.elapsedRealtime();
             int stateCount = in.readInt();
             if (stateCount != 0) {
                 final TimeMultiStateCounter counter = new TimeMultiStateCounter(
@@ -10360,7 +10306,7 @@
                 // Read the object from the Parcel, whether it's usable or not
                 TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
                         mBsi.mOnBatteryTimeBase, in, timestampMs);
-                if (stateCount == NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     mProcStateTimeMs = counter;
                 }
             } else {
@@ -10373,7 +10319,7 @@
                 TimeInFreqMultiStateCounter counter =
                         new TimeInFreqMultiStateCounter(
                                 mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
-                if (stateCount == NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     mProcStateScreenOffTimeMs = counter;
                 }
             } else {
@@ -11244,12 +11190,6 @@
         }
 
         @GuardedBy("mBsi")
-        public void updateUidProcessStateLocked(int procState) {
-            updateUidProcessStateLocked(procState,
-                    mBsi.mClock.elapsedRealtime(), mBsi.mClock.uptimeMillis());
-        }
-
-        @GuardedBy("mBsi")
         public void updateUidProcessStateLocked(int procState,
                 long elapsedRealtimeMs, long uptimeMs) {
             int uidRunningState;
@@ -11263,47 +11203,49 @@
             }
 
             if (mProcessState != uidRunningState) {
-                if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                if (mProcessState != Uid.PROCESS_STATE_NONEXISTENT) {
                     mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
-
-                    if (mBsi.trackPerProcStateCpuTimes()) {
-                        if (mBsi.mPendingUids.size() == 0) {
-                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
-                                    mBsi.mOnBatteryTimeBase.isRunning(),
-                                    mBsi.mOnBatteryScreenOffTimeBase.isRunning(),
-                                    mBsi.mConstants.PROC_STATE_CPU_TIMES_READ_DELAY_MS);
-                            mBsi.mNumSingleUidCpuTimeReads++;
-                        } else {
-                            mBsi.mNumBatchedSingleUidCpuTimeReads++;
-                        }
-                        if (mBsi.mPendingUids.indexOfKey(mUid) < 0
-                                || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
-                            mBsi.mPendingUids.put(mUid, mProcessState);
-                        }
-                    } else {
-                        mBsi.mPendingUids.clear();
-                    }
                 }
-                mProcessState = uidRunningState;
-                if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                if (uidRunningState != Uid.PROCESS_STATE_NONEXISTENT) {
                     if (mProcessStateTimer[uidRunningState] == null) {
                         makeProcessState(uidRunningState, null);
                     }
                     mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
                 }
 
+                if (mBsi.trackPerProcStateCpuTimes()) {
+                    mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+
+                    LongArrayMultiStateCounter onBatteryCounter =
+                            getProcStateTimeCounter().getCounter();
+                    LongArrayMultiStateCounter onBatteryScreenOffCounter =
+                            getProcStateScreenOffTimeCounter().getCounter();
+
+                    onBatteryCounter.setState(uidRunningState, elapsedRealtimeMs);
+                    onBatteryScreenOffCounter.setState(uidRunningState, elapsedRealtimeMs);
+                }
+
+                final int prevBatteryConsumerProcessState =
+                        mapUidProcessStateToBatteryConsumerProcessState(mProcessState);
+
+                mProcessState = uidRunningState;
+
                 updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
                 updateOnBatteryScreenOffBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
 
                 final int batteryConsumerProcessState =
-                        mapUidProcessStateToBatteryConsumerProcessState(mProcessState);
+                        mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
                 getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
 
+                getMobileRadioActiveTimeCounter()
+                        .setState(batteryConsumerProcessState, elapsedRealtimeMs);
                 final MeasuredEnergyStats energyStats =
                         getOrCreateMeasuredEnergyStatsIfSupportedLocked();
                 if (energyStats != null) {
                     energyStats.setState(batteryConsumerProcessState, elapsedRealtimeMs);
                 }
+                maybeScheduleExternalStatsSync(prevBatteryConsumerProcessState,
+                        batteryConsumerProcessState);
             }
 
             if (userAwareService != mInForegroundService) {
@@ -11316,9 +11258,30 @@
             }
         }
 
+        @GuardedBy("mBsi")
+        private void maybeScheduleExternalStatsSync(
+                @BatteryConsumer.ProcessState int oldProcessState,
+                @BatteryConsumer.ProcessState int newProcessState) {
+            if (oldProcessState == newProcessState) {
+                return;
+            }
+            // Transitions between BACKGROUND and such non-foreground states like cached
+            // or nonexistent do not warrant doing a sync.  If some of the stats for those
+            // proc states bleed into the PROCESS_STATE_BACKGROUND, that's ok.
+            if ((oldProcessState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED
+                    && newProcessState == BatteryConsumer.PROCESS_STATE_BACKGROUND)
+                    || (oldProcessState == BatteryConsumer.PROCESS_STATE_BACKGROUND
+                    && newProcessState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED)) {
+                return;
+            }
+
+            mBsi.mExternalSync.scheduleSyncDueToProcessStateChange(
+                    mBsi.mConstants.PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
+        }
+
         /** Whether to consider Uid to be in the background for background timebase purposes. */
         public boolean isInBackground() {
-            // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+            // Note that PROCESS_STATE_CACHED and Uid.PROCESS_STATE_NONEXISTENT is
             // also considered to be 'background' for our purposes, because it's not foreground.
             return mProcessState >= PROCESS_STATE_BACKGROUND;
         }
@@ -12913,7 +12876,8 @@
                             .calcGlobalPowerWithoutControllerDataMah(globalTimeMs);
                 }
                 distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_WIFI,
-                        consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah);
+                        consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah,
+                        elapsedRealtimeMs);
             }
         }
     }
@@ -13071,7 +13035,7 @@
                         final long appPackets = entry.rxPackets + entry.txPackets;
                         final long appRadioTimeUs =
                                 (totalAppRadioTimeUs * appPackets) / totalPackets;
-                        u.noteMobileRadioActiveTimeLocked(appRadioTimeUs);
+                        u.noteMobileRadioActiveTimeLocked(appRadioTimeUs, elapsedRealtimeMs);
 
                         // Distribute measured mobile radio charge consumption based on app radio
                         // active time
@@ -13150,7 +13114,7 @@
 
                     distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
                             consumedChargeUC, uidEstimatedConsumptionMah,
-                            totalEstimatedConsumptionMah);
+                            totalEstimatedConsumptionMah, elapsedRealtimeMs);
                 }
 
                 mNetworkStatsPool.release(delta);
@@ -13438,7 +13402,8 @@
                     = mBluetoothPowerCalculator.calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
             totalEstimatedMah = Math.max(totalEstimatedMah, controllerMaMs / MILLISECONDS_IN_HOUR);
             distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
-                    consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah);
+                    consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah,
+                    elapsedRealtimeMs);
         }
 
         mLastBluetoothActivityInfo.set(info);
@@ -13541,8 +13506,10 @@
         }
         if (totalCpuChargeUC <= 0) return;
 
+        final long timestampMs = mClock.elapsedRealtime();
+
         mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_CPU,
-                totalCpuChargeUC, mClock.elapsedRealtime());
+                totalCpuChargeUC, timestampMs);
 
         // Calculate the measured microcoulombs/calculated milliamp-hour charge ratio for each
         // cluster to normalize  each uid's estimated power usage against actual power usage for
@@ -13591,7 +13558,7 @@
             }
 
             uid.addChargeToStandardBucketLocked(uidCpuChargeUC,
-                    MeasuredEnergyStats.POWER_BUCKET_CPU);
+                    MeasuredEnergyStats.POWER_BUCKET_CPU, timestampMs);
         }
     }
 
@@ -13691,7 +13658,7 @@
             fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs);
         }
         distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON,
-                totalScreenOnChargeUC, fgTimeUsArray, 0);
+                totalScreenOnChargeUC, fgTimeUsArray, 0, elapsedRealtimeMs);
     }
 
     /**
@@ -13735,7 +13702,7 @@
             gnssTimeUsArray.put(uid.getUid(), (double) gnssTimeUs);
         }
         distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_GNSS, chargeUC,
-                gnssTimeUsArray, 0);
+                gnssTimeUsArray, 0, elapsedRealtimeMs);
     }
 
     /**
@@ -13807,7 +13774,7 @@
     @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToStandardBucketLocked
     private void distributeEnergyToUidsLocked(@StandardPowerBucket int bucket,
             long totalConsumedChargeUC, SparseDoubleArray ratioNumerators,
-            double minRatioDenominator) {
+            double minRatioDenominator, long timestampMs) {
 
         // If the sum of all app usage was greater than the total, use that instead:
         double sumRatioNumerators = 0;
@@ -13822,7 +13789,7 @@
             final double ratioNumerator = ratioNumerators.valueAt(i);
             final long uidActualUC
                     = (long) (totalConsumedChargeUC * ratioNumerator / ratioDenominator + 0.5);
-            uid.addChargeToStandardBucketLocked(uidActualUC, bucket);
+            uid.addChargeToStandardBucketLocked(uidActualUC, bucket, timestampMs);
         }
     }
 
@@ -14541,7 +14508,7 @@
                 if (childUid != null) {
                     final long delta =
                             childUid.cpuActiveCounter.update(cpuActiveTimesMs, elapsedRealtimeMs);
-                    u.getCpuActiveTimeCounter().addCount(delta);
+                    u.getCpuActiveTimeCounter().increment(delta, elapsedRealtimeMs);
                 }
             }
         });
@@ -15660,7 +15627,7 @@
 
     @GuardedBy("this")
     public boolean trackPerProcStateCpuTimes() {
-        return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
+        return mCpuUidFreqTimeReader.isFastCpuTimesReader();
     }
 
     @GuardedBy("this")
@@ -15735,7 +15702,6 @@
         for (int procState = 0; procState < BatteryConsumer.PROCESS_STATE_COUNT; procState++) {
             procStateNames[procState] = BatteryConsumer.processStateToString(procState);
         }
-        procStateNames[BatteryConsumer.PROCESS_STATE_ANY] = "untracked";
         return procStateNames;
     }
 
@@ -15747,8 +15713,6 @@
 
     @VisibleForTesting
     public final class Constants extends ContentObserver {
-        public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
-                = "track_cpu_times_by_proc_state";
         public static final String KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME
                 = "track_cpu_active_cluster_time";
         public static final String KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
@@ -15761,27 +15725,26 @@
                 = "external_stats_collection_rate_limit_ms";
         public static final String KEY_BATTERY_LEVEL_COLLECTION_DELAY_MS
                 = "battery_level_collection_delay_ms";
+        public static final String KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS =
+                "procstate_change_collection_delay_ms";
         public static final String KEY_MAX_HISTORY_FILES = "max_history_files";
         public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb";
         public static final String KEY_BATTERY_CHARGED_DELAY_MS =
                 "battery_charged_delay_ms";
 
-        private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
         private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
-        private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
         private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
         private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
         private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
         private static final long DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS = 300_000;
+        private static final long DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS = 60_000;
         private static final int DEFAULT_MAX_HISTORY_FILES = 32;
         private static final int DEFAULT_MAX_HISTORY_BUFFER_KB = 128; /*Kilo Bytes*/
         private static final int DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE = 64;
         private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
         private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
 
-        public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
         public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
-        public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
         /* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
          * update when startObserving. */
         public long KERNEL_UID_READERS_THROTTLE_TIME;
@@ -15790,6 +15753,8 @@
                 = DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS;
         public long BATTERY_LEVEL_COLLECTION_DELAY_MS
                 = DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS;
+        public long PROC_STATE_CHANGE_COLLECTION_DELAY_MS =
+                DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS;
         public int MAX_HISTORY_FILES;
         public int MAX_HISTORY_BUFFER; /*Bytes*/
         public int BATTERY_CHARGED_DELAY_MS = DEFAULT_BATTERY_CHARGED_DELAY_MS;
@@ -15843,14 +15808,8 @@
                     Slog.e(TAG, "Bad batterystats settings", e);
                 }
 
-                updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
-                        mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
-                                DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
                 TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean(
                         KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME);
-                updateProcStateCpuTimesReadDelayMs(PROC_STATE_CPU_TIMES_READ_DELAY_MS,
-                        mParser.getLong(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS,
-                                DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
                 updateKernelUidReadersThrottleTime(KERNEL_UID_READERS_THROTTLE_TIME,
                         mParser.getLong(KEY_KERNEL_UID_READERS_THROTTLE_TIME,
                                 DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME));
@@ -15862,6 +15821,9 @@
                 BATTERY_LEVEL_COLLECTION_DELAY_MS = mParser.getLong(
                         KEY_BATTERY_LEVEL_COLLECTION_DELAY_MS,
                         DEFAULT_BATTERY_LEVEL_COLLECTION_DELAY_MS);
+                PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
+                        KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
+                        DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
 
                 MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
                         ActivityManager.isLowRamDeviceStatic() ?
@@ -15887,33 +15849,6 @@
                     DEFAULT_BATTERY_CHARGED_DELAY_MS);
         }
 
-        @GuardedBy("BatteryStatsImpl.this")
-        private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
-            TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
-            if (isEnabled && !wasEnabled) {
-                mIsPerProcessStateCpuDataStale = true;
-                mExternalSync.scheduleCpuSyncDueToSettingChange();
-
-                mNumSingleUidCpuTimeReads = 0;
-                mNumBatchedSingleUidCpuTimeReads = 0;
-                mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
-            }
-            final long timestampMs = mClock.elapsedRealtime();
-            for (int i = mUidStats.size() - 1; i >= 0; i--) {
-                mUidStats.valueAt(i).setProcStateTimesTrackingEnabled(isEnabled, timestampMs);
-            }
-        }
-
-        @GuardedBy("BatteryStatsImpl.this")
-        private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
-            PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
-            if (oldDelayMillis != newDelayMillis) {
-                mNumSingleUidCpuTimeReads = 0;
-                mNumBatchedSingleUidCpuTimeReads = 0;
-                mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
-            }
-        }
-
         private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
             KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
             if (oldTimeMs != newTimeMs) {
@@ -15932,18 +15867,16 @@
         }
 
         public void dumpLocked(PrintWriter pw) {
-            pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
-            pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
             pw.print(KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME); pw.print("=");
             pw.println(TRACK_CPU_ACTIVE_CLUSTER_TIME);
-            pw.print(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS); pw.print("=");
-            pw.println(PROC_STATE_CPU_TIMES_READ_DELAY_MS);
             pw.print(KEY_KERNEL_UID_READERS_THROTTLE_TIME); pw.print("=");
             pw.println(KERNEL_UID_READERS_THROTTLE_TIME);
             pw.print(KEY_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS); pw.print("=");
             pw.println(EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS);
             pw.print(KEY_BATTERY_LEVEL_COLLECTION_DELAY_MS); pw.print("=");
             pw.println(BATTERY_LEVEL_COLLECTION_DELAY_MS);
+            pw.print(KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS); pw.print("=");
+            pw.println(PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
             pw.print(KEY_MAX_HISTORY_FILES); pw.print("=");
             pw.println(MAX_HISTORY_FILES);
             pw.print(KEY_MAX_HISTORY_BUFFER_KB); pw.print("=");
@@ -16606,8 +16539,8 @@
             if (in.readInt() != 0) {
                 u.createBluetoothScanResultBgCounterLocked().readSummaryFromParcelLocked(in);
             }
-            u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-            for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+            u.mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
+            for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                 if (in.readInt() != 0) {
                     u.makeProcessState(i, null);
                     u.mProcessStateTimer[i].readSummaryFromParcelLocked(in);
@@ -16627,14 +16560,18 @@
             }
 
             if (in.readInt() != 0) {
-                if (u.mNetworkByteActivityCounters == null) {
-                    u.initNetworkActivityLocked();
-                }
+                u.ensureNetworkActivityLocked();
                 for (int i = 0; i < NUM_NETWORK_ACTIVITY_TYPES; i++) {
                     u.mNetworkByteActivityCounters[i].readSummaryFromParcelLocked(in);
                     u.mNetworkPacketActivityCounters[i].readSummaryFromParcelLocked(in);
                 }
-                u.mMobileRadioActiveTime.readSummaryFromParcelLocked(in);
+                if (in.readBoolean()) {
+                    TimeMultiStateCounter counter = new TimeMultiStateCounter(
+                            mOnBatteryTimeBase, in, elapsedRealtimeMs);
+                    if (counter.getStateCount() == BatteryConsumer.PROCESS_STATE_COUNT) {
+                        u.mMobileRadioActiveTime = counter;
+                    }
+                }
                 u.mMobileRadioActiveCount.readSummaryFromParcelLocked(in);
             }
 
@@ -16699,7 +16636,7 @@
                 // Read the object from the Parcel, whether it's usable or not
                 TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
                         mOnBatteryTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == Uid.NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     u.mProcStateTimeMs = counter;
                 }
             }
@@ -16714,7 +16651,7 @@
                 TimeInFreqMultiStateCounter counter =
                         new TimeInFreqMultiStateCounter(
                                 mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
-                if (stateCount == Uid.NUM_PROCESS_STATE) {
+                if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
                     u.mProcStateScreenOffTimeMs = counter;
                 }
             }
@@ -17152,7 +17089,7 @@
             } else {
                 out.writeInt(0);
             }
-            for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+            for (int i = 0; i < NUM_PROCESS_STATE; i++) {
                 if (u.mProcessStateTimer[i] != null) {
                     out.writeInt(1);
                     u.mProcessStateTimer[i].writeSummaryFromParcelLocked(out, nowRealtime);
@@ -17184,7 +17121,12 @@
                     u.mNetworkByteActivityCounters[i].writeSummaryFromParcelLocked(out);
                     u.mNetworkPacketActivityCounters[i].writeSummaryFromParcelLocked(out);
                 }
-                u.mMobileRadioActiveTime.writeSummaryFromParcelLocked(out);
+                if (u.mMobileRadioActiveTime != null) {
+                    out.writeBoolean(true);
+                    u.mMobileRadioActiveTime.writeToParcel(out);
+                } else {
+                    out.writeBoolean(false);
+                }
                 u.mMobileRadioActiveCount.writeSummaryFromParcelLocked(out);
             }
 
@@ -17982,10 +17924,10 @@
         }
         super.dumpLocked(context, pw, flags, reqUid, histStart);
 
+        pw.print("Per process state tracking available: ");
+        pw.println(trackPerProcStateCpuTimes());
         pw.print("Total cpu time reads: ");
         pw.println(mNumSingleUidCpuTimeReads);
-        pw.print("Batched cpu time reads: ");
-        pw.println(mNumBatchedSingleUidCpuTimeReads);
         pw.print("Batching Duration (min): ");
         pw.println((mClock.uptimeMillis() - mCpuTimeReadsTrackingStartTimeMs) / (60 * 1000));
         pw.print("All UID cpu time reads since the later of device start or stats reset: ");
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 175f28f..ee614cd 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -156,11 +156,11 @@
     private void calculateMeasuredPowerPerProcessState(UidBatteryConsumer.Builder app,
             BatteryStats.Uid u, BatteryConsumer.Key[] keys) {
         for (BatteryConsumer.Key key : keys) {
-            // The key for "PROCESS_STATE_ANY" has already been populated with the
-            // full energy across all states.  We don't want to override it with
+            // The key for PROCESS_STATE_UNSPECIFIED aka PROCESS_STATE_ANY has already been
+            // populated with the full energy across all states.  We don't want to override it with
             // the energy for "other" states, which excludes the tracked states like
             // foreground, background etc.
-            if (key.processState == BatteryConsumer.PROCESS_STATE_ANY) {
+            if (key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                 continue;
             }
 
@@ -184,7 +184,7 @@
                 uidProcState++) {
             @BatteryConsumer.ProcessState int procState =
                     BatteryStats.mapUidProcessStateToBatteryConsumerProcessState(uidProcState);
-            if (procState == BatteryConsumer.PROCESS_STATE_ANY) {
+            if (procState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                 continue;
             }
 
@@ -199,7 +199,7 @@
         }
 
         for (BatteryConsumer.Key key : keys) {
-            if (key.processState == BatteryConsumer.PROCESS_STATE_ANY) {
+            if (key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
                 continue;
             }
 
diff --git a/core/java/com/android/internal/os/DmabufInfoReader.java b/core/java/com/android/internal/os/KernelAllocationStats.java
similarity index 72%
rename from core/java/com/android/internal/os/DmabufInfoReader.java
rename to core/java/com/android/internal/os/KernelAllocationStats.java
index 786a6ee..1c3f8b0 100644
--- a/core/java/com/android/internal/os/DmabufInfoReader.java
+++ b/core/java/com/android/internal/os/KernelAllocationStats.java
@@ -18,9 +18,9 @@
 
 import android.annotation.Nullable;
 
-/** Wrapper around libdmabufinfo. */
-public final class DmabufInfoReader {
-    private DmabufInfoReader() {}
+/** JNI wrapper around libmeminfo for kernel memory allocation stats (dmabufs, gpu driver). */
+public final class KernelAllocationStats {
+    private KernelAllocationStats() {}
 
     /** Process dma-buf stats. */
     public static final class ProcessDmabuf {
@@ -47,5 +47,19 @@
      * stats could not be read.
      */
     @Nullable
-    public static native ProcessDmabuf getProcessStats(int pid);
+    public static native ProcessDmabuf getDmabufAllocations(int pid);
+
+    /** Pid to gpu memory size. */
+    public static final class ProcessGpuMem {
+        public final int pid;
+        public final int gpuMemoryKb;
+
+        ProcessGpuMem(int pid, int gpuMemoryKb) {
+            this.pid = pid;
+            this.gpuMemoryKb = gpuMemoryKb;
+        }
+    }
+
+    /** Return list of pid to gpu memory size. */
+    public static native ProcessGpuMem[] getGpuAllocations();
 }
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index faeb8fc..c801be0 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -620,6 +620,10 @@
             }
             return numClusterFreqs;
         }
+
+        public boolean isFastCpuTimesReader() {
+            return mBpfTimesAvailable;
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java
index ad8e0ed..33a9d54 100644
--- a/core/java/com/android/internal/os/LongMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -122,6 +122,13 @@
     /**
      * Adds the supplied values to the current accumulated values in the counter.
      */
+    public void incrementValue(long count, long timestampMs) {
+        native_incrementValue(mNativeObject, count, timestampMs);
+    }
+
+    /**
+     * Adds the supplied values to the current accumulated values in the counter.
+     */
     public void addCount(long count) {
         native_addCount(mNativeObject, count);
     }
@@ -144,6 +151,17 @@
         return native_getCount(mNativeObject, state);
     }
 
+    /**
+     * Returns the total accumulated count across all states.
+     */
+    public long getTotalCount() {
+        long total = 0;
+        for (int state = 0; state < mStateCount; state++) {
+            total += native_getCount(mNativeObject, state);
+        }
+        return total;
+    }
+
     @Override
     public String toString() {
         return native_toString(mNativeObject);
@@ -190,6 +208,10 @@
     private static native long native_updateValue(long nativeObject, long value, long timestampMs);
 
     @CriticalNative
+    private static native void native_incrementValue(long nativeObject, long increment,
+            long timestampMs);
+
+    @CriticalNative
     private static native void native_addCount(long nativeObject, long count);
 
     @CriticalNative
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index eb5993d..28cc836 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -34,6 +34,8 @@
     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
             CellSignalStrength.getNumSignalStrengthLevels();
 
+    private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+
     private final UsageBasedPowerEstimator mActivePowerEstimator;
     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
             new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
@@ -89,14 +91,22 @@
 
         PowerAndDuration total = new PowerAndDuration();
 
-        final double powerPerPacketMah = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED);
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
+        BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
+
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             final BatteryStats.Uid uid = app.getBatteryStatsUid();
-            calculateApp(app, uid, powerPerPacketMah, total, query);
+            if (keys == UNINITIALIZED_KEYS) {
+                if (query.isProcessStateDataNeeded()) {
+                    keys = app.getKeys(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO);
+                } else {
+                    keys = null;
+                }
+            }
+
+            calculateApp(app, uid, total, query, keys);
         }
 
         final long consumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
@@ -121,34 +131,49 @@
     }
 
     private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            double powerPerPacketMah, PowerAndDuration total,
-            BatteryUsageStatsQuery query) {
+            PowerAndDuration total,
+            BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
         final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
         total.totalAppDurationMs += radioActiveDurationMs;
 
         final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
-        final double powerMah = calculatePower(u, powerModel, powerPerPacketMah,
-                radioActiveDurationMs, consumptionUC);
+        final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
         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);
+            }
+        }
     }
 
     @Override
     public void calculate(List<BatterySipper> sippers, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, int statsType, SparseArray<UserHandle> asUsers) {
-        final double mobilePowerPerPacket = getMobilePowerPerPacket(batteryStats, rawRealtimeUs,
-                statsType);
         PowerAndDuration total = new PowerAndDuration();
         for (int i = sippers.size() - 1; i >= 0; i--) {
             final BatterySipper app = sippers.get(i);
             if (app.drainType == BatterySipper.DrainType.APP) {
                 final BatteryStats.Uid u = app.uidObj;
-                calculateApp(app, u, statsType, mobilePowerPerPacket, total);
+                calculateApp(app, u, statsType, total);
             }
         }
 
@@ -172,13 +197,12 @@
     }
 
     private void calculateApp(BatterySipper app, BatteryStats.Uid u, int statsType,
-            double powerPerPacketMah, PowerAndDuration total) {
+            PowerAndDuration total) {
         app.mobileActive = calculateDuration(u, statsType);
 
         final long consumptionUC =  u.getMobileRadioMeasuredBatteryConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC);
-        app.mobileRadioPowerMah = calculatePower(u, powerModel, powerPerPacketMah, app.mobileActive,
-                consumptionUC);
+        app.mobileRadioPowerMah = calculatePower(u, powerModel, app.mobileActive, consumptionUC);
         total.totalAppDurationMs += app.mobileActive;
 
         // Add cost of mobile traffic.
@@ -205,26 +229,15 @@
     }
 
     private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
-            double powerPerPacketMah, long radioActiveDurationMs, long measuredChargeUC) {
+            long radioActiveDurationMs, long measuredChargeUC) {
         if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
             return uCtoMah(measuredChargeUC);
         }
 
         if (radioActiveDurationMs > 0) {
-            // We are tracking when the radio is up, so can use the active time to
-            // determine power use.
             return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
-        } else {
-            // We are not tracking when the radio is up, so must approximate power use
-            // based on the number of packets.
-            final long mobileRxPackets = u.getNetworkActivityPackets(
-                    BatteryStats.NETWORK_MOBILE_RX_DATA,
-                    BatteryStats.STATS_SINCE_CHARGED);
-            final long mobileTxPackets = u.getNetworkActivityPackets(
-                    BatteryStats.NETWORK_MOBILE_TX_DATA,
-                    BatteryStats.STATS_SINCE_CHARGED);
-            return (mobileRxPackets + mobileTxPackets) * powerPerPacketMah;
         }
+        return 0;
     }
 
     private void calculateRemaining(PowerAndDuration total,
@@ -299,21 +312,4 @@
     public double calcScanTimePowerMah(long scanningTimeMs) {
         return mScanPowerEstimator.calculatePower(scanningTimeMs);
     }
-
-    /**
-     * Return estimated power (in mAh) of sending or receiving a packet with the mobile radio.
-     */
-    private double getMobilePowerPerPacket(BatteryStats stats, long rawRealtimeUs, int statsType) {
-        final long radioDataUptimeMs =
-                stats.getMobileRadioActiveTime(rawRealtimeUs, statsType) / 1000;
-        final double mobilePower = calcPowerFromRadioActiveDurationMah(radioDataUptimeMs);
-
-        final long mobileRx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_RX_DATA,
-                statsType);
-        final long mobileTx = stats.getNetworkActivityPackets(BatteryStats.NETWORK_MOBILE_TX_DATA,
-                statsType);
-        final long mobilePackets = mobileRx + mobileTx;
-
-        return mobilePackets != 0 ? mobilePower / mobilePackets : 0;
-    }
 }
diff --git a/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java
new file mode 100644
index 0000000..615e4b79
--- /dev/null
+++ b/core/java/com/android/internal/os/SystemServerClassLoaderFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.os.Build;
+import android.util.ArrayMap;
+
+import dalvik.system.PathClassLoader;
+
+/** @hide */
+public final class SystemServerClassLoaderFactory {
+    /**
+     * Map of paths to PathClassLoader for standalone system server jars.
+     */
+    private static final ArrayMap<String, PathClassLoader> sLoadedPaths = new ArrayMap<>();
+
+    /**
+     * Creates and caches a ClassLoader for the jar at the given path, or returns a cached
+     * ClassLoader if it exists.
+     *
+     * The parent class loader should always be the system server class loader. Changing it has
+     * implications that require discussion with the mainline team.
+     *
+     * @hide for internal use only
+     */
+    public static PathClassLoader getOrCreateClassLoader(String path, ClassLoader parent) {
+        PathClassLoader pathClassLoader = sLoadedPaths.get(path);
+        if (pathClassLoader == null) {
+            pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader(
+                    path, /*librarySearchPath=*/null, /*libraryPermittedPath=*/null, parent,
+                    Build.VERSION.SDK_INT, /*isNamespaceShared=*/true , /*classLoaderName=*/null);
+            sLoadedPaths.put(path, pathClassLoader);
+        }
+        return pathClassLoader;
+    }
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 29e5a5a..611f644 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -46,6 +46,7 @@
 import android.system.StructCapUserData;
 import android.system.StructCapUserHeader;
 import android.text.Hyphenator;
+import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
@@ -560,9 +561,8 @@
 
     /**
      * Create the classloader for the system server and store it in
-     * {@link sCachedSystemServerClassLoader}. This function may be called through JNI in
-     * system server startup, when the runtime is in a critically low state. Do not do
-     * extended computation etc here.
+     * {@link sCachedSystemServerClassLoader}. This function is called through JNI in the forked
+     * system server process in the zygote SELinux domain.
      */
     private static ClassLoader getOrCreateSystemServerClassLoader() {
         if (sCachedSystemServerClassLoader == null) {
@@ -576,6 +576,29 @@
     }
 
     /**
+     * Creates class loaders for standalone system server jars. This function is called through JNI
+     * in the forked system server process in the zygote SELinux domain.
+     */
+    private static void prefetchStandaloneSystemServerJars() {
+        String envStr = Os.getenv("STANDALONE_SYSTEMSERVER_JARS");
+        if (TextUtils.isEmpty(envStr)) {
+            return;
+        }
+        for (String jar : envStr.split(":")) {
+            try {
+                SystemServerClassLoaderFactory.getOrCreateClassLoader(
+                        jar, getOrCreateSystemServerClassLoader());
+            } catch (Error e) {
+                // We don't want the process to crash for this error because prefetching is just an
+                // optimization.
+                Log.e(TAG,
+                        String.format("Failed to prefetch standalone system server jar \"%s\": %s",
+                                jar, e.toString()));
+            }
+        }
+    }
+
+    /**
      * Note that preparing the profiles for system server does not require special selinux
      * permissions. From the installer perspective the system server is a regular package which can
      * capture profile information.
@@ -690,7 +713,7 @@
                 "--setuid=1000",
                 "--setgid=1000",
                 "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
-                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011",
+                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011,3012",
                 "--capabilities=" + capabilities + "," + capabilities,
                 "--nice-name=system_server",
                 "--runtime-args",
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 424632fe..6541b14 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -270,8 +270,6 @@
     private Drawable mCaptionBackgroundDrawable;
     private Drawable mUserCaptionBackgroundDrawable;
 
-    private float mAvailableWidth;
-
     String mLogTag = TAG;
     private final Rect mFloatingInsets = new Rect();
     private boolean mApplyFloatingVerticalInsets = false;
@@ -315,8 +313,6 @@
         mSemiTransparentBarColor = context.getResources().getColor(
                 R.color.system_bar_background_semi_transparent, null /* theme */);
 
-        updateAvailableWidth();
-
         setWindow(window);
 
         updateLogTag(params);
@@ -697,7 +693,8 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+        final Resources res = getContext().getResources();
+        final DisplayMetrics metrics = res.getDisplayMetrics();
         final boolean isPortrait =
                 getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
 
@@ -767,17 +764,19 @@
 
         if (!fixedWidth && widthMode == AT_MOST) {
             final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
+            final float availableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    res.getConfiguration().screenWidthDp, metrics);
             if (tv.type != TypedValue.TYPE_NULL) {
                 final int min;
                 if (tv.type == TypedValue.TYPE_DIMENSION) {
-                    min = (int)tv.getDimension(metrics);
+                    min = (int) tv.getDimension(metrics);
                 } else if (tv.type == TypedValue.TYPE_FRACTION) {
-                    min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth);
+                    min = (int) tv.getFraction(availableWidth, availableWidth);
                 } else {
                     min = 0;
                 }
                 if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::"
-                        + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth);
+                        + tv.coerceToString() + ", mAvailableWidth=" + availableWidth);
 
                 if (width < min) {
                     widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
@@ -2144,7 +2143,6 @@
 
         updateDecorCaptionStatus(newConfig);
 
-        updateAvailableWidth();
         initializeElevation();
     }
 
@@ -2616,12 +2614,6 @@
         mLogTag = TAG + "[" + getTitleSuffix(params) + "]";
     }
 
-    private void updateAvailableWidth() {
-        Resources res = getResources();
-        mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                res.getConfiguration().screenWidthDp, res.getDisplayMetrics());
-    }
-
     /**
      * @hide
      */
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/MeasuredEnergyStats.java
index 4dc9aa5..a52ae10 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/MeasuredEnergyStats.java
@@ -589,6 +589,10 @@
         final int numIndices = mConfig.getNumberOfBuckets();
         for (int index = 0; index < numIndices; index++) {
             setValueIfSupported(index, 0L);
+            if (mAccumulatedMultiStateChargeMicroCoulomb != null
+                    && mAccumulatedMultiStateChargeMicroCoulomb[index] != null) {
+                mAccumulatedMultiStateChargeMicroCoulomb[index].reset();
+            }
         }
     }
 
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 1d62623..4f80afa 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -81,9 +81,9 @@
     }
 
     public void readFromParcel(Parcel in) {
-        this.icon = (Icon) in.readParcelable(null);
+        this.icon = (Icon) in.readParcelable(null, android.graphics.drawable.Icon.class);
         this.pkg = in.readString();
-        this.user = (UserHandle) in.readParcelable(null);
+        this.user = (UserHandle) in.readParcelable(null, android.os.UserHandle.class);
         this.iconLevel = in.readInt();
         this.visible = in.readInt() != 0;
         this.number = in.readInt();
diff --git a/media/java/android/media/tv/TsRequest.aidl b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
similarity index 74%
copy from media/java/android/media/tv/TsRequest.aidl
copy to core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
index 0abc7ec..6ca8cec 100644
--- a/media/java/android/media/tv/TsRequest.aidl
+++ b/core/java/com/android/internal/telephony/ICarrierPrivilegesListener.aidl
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv;
+package com.android.internal.telephony;
 
-parcelable TsRequest;
+oneway interface ICarrierPrivilegesListener {
+    void onCarrierPrivilegesChanged(
+            in List<String> privilegedPackageNames, in int[] privilegedUids);
+}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index a0a0f32..9712d7e 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 
@@ -43,7 +44,8 @@
     void removeOnSubscriptionsChangedListener(String pkg,
             IOnSubscriptionsChangedListener callback);
 
-    void listenWithEventList(in int subId, String pkg, String featureId,
+    void listenWithEventList(in boolean renounceFineLocationAccess,
+            in boolean renounceCoarseLocationAccess, in int subId, String pkg, String featureId,
             IPhoneStateListener callback, in int[] events, boolean notifyNow);
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void notifyCallStateForAllSubs(int state, String incomingNumber);
@@ -76,6 +78,7 @@
     void notifySubscriptionInfoChanged();
     void notifyOpportunisticSubscriptionInfoChanged();
     void notifyCarrierNetworkChange(in boolean active);
+    void notifyCarrierNetworkChangeWithSubId(in int subId, in boolean active);
     void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
     void notifyDisplayInfoChanged(int slotIndex, int subId, in TelephonyDisplayInfo telephonyDisplayInfo);
     void notifyPhoneCapabilityChanged(in PhoneCapability capability);
@@ -98,4 +101,10 @@
     void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
     void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
             in List<LinkCapacityEstimate> linkCapacityEstimateList);
+
+    void addCarrierPrivilegesListener(
+            int phoneId, ICarrierPrivilegesListener callback, String pkg, String featureId);
+    void removeCarrierPrivilegesListener(ICarrierPrivilegesListener callback, String pkg);
+    void notifyCarrierPrivilegesChanged(
+            int phoneId, in List<String> privilegedPackageNames, in int[] privilegedUids);
 }
diff --git a/core/java/com/android/internal/util/AnnotationValidations.java b/core/java/com/android/internal/util/AnnotationValidations.java
index cf5e48f..e1b3802 100644
--- a/core/java/com/android/internal/util/AnnotationValidations.java
+++ b/core/java/com/android/internal/util/AnnotationValidations.java
@@ -26,7 +26,7 @@
 import android.annotation.UserIdInt;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.PackageInfoFlags;
+import android.content.pm.PackageManager.PackageInfoFlagsBits;
 import android.content.pm.PackageManager.PermissionResult;
 import android.os.UserHandle;
 
@@ -160,8 +160,8 @@
     }
 
     public static void validate(
-            Class<PackageInfoFlags> annotation, PackageInfoFlags ignored, int value) {
-        validateIntFlags(annotation, value,
+            Class<PackageInfoFlagsBits> annotation, PackageInfoFlagsBits ignored, long value) {
+        validateLongFlags(annotation, value,
                 flagsUpTo(PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS));
     }
 
@@ -212,7 +212,12 @@
             invalid(annotation, "0x" + Integer.toHexString(value));
         }
     }
-
+    private static void validateLongFlags(
+            Class<? extends Annotation> annotation, long value, int validBits) {
+        if ((validBits & value) != validBits) {
+            invalid(annotation, "0x" + Long.toHexString(value));
+        }
+    }
     private static void invalid(Class<? extends Annotation> annotation, Object value) {
         invalid("@" + annotation.getSimpleName(), value);
     }
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 4c519f4..e95ba88 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -122,6 +122,11 @@
      */
     public static final int ACTION_USER_SWITCH = 12;
 
+    /**
+     * Time it takes to turn on the inner screen for a foldable device.
+     */
+    public static final int ACTION_SWITCH_DISPLAY_UNFOLD = 13;
+
     private static final int[] ACTIONS_ALL = {
         ACTION_EXPAND_PANEL,
         ACTION_TOGGLE_RECENTS,
@@ -135,7 +140,8 @@
         ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_ROTATE_SCREEN_CAMERA_CHECK,
         ACTION_LOCKSCREEN_UNLOCK,
-        ACTION_USER_SWITCH
+        ACTION_USER_SWITCH,
+        ACTION_SWITCH_DISPLAY_UNFOLD
     };
 
     /** @hide */
@@ -152,7 +158,8 @@
         ACTION_ROTATE_SCREEN_SENSOR,
         ACTION_ROTATE_SCREEN_CAMERA_CHECK,
         ACTION_LOCKSCREEN_UNLOCK,
-        ACTION_USER_SWITCH
+        ACTION_USER_SWITCH,
+        ACTION_SWITCH_DISPLAY_UNFOLD
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Action {
@@ -171,7 +178,8 @@
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK,
             FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK,
-            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH
+            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH,
+            FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SWITCH_DISPLAY_UNFOLD
     };
 
     private static LatencyTracker sLatencyTracker;
@@ -257,6 +265,8 @@
                 return "ACTION_LOCKSCREEN_UNLOCK";
             case 13:
                 return "ACTION_USER_SWITCH";
+            case 14:
+                return "ACTION_SWITCH_DISPLAY_UNFOLD";
             default:
                 throw new IllegalArgumentException("Invalid action");
         }
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 4ad7795..04e4819 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -71,11 +71,11 @@
 
             if (in.readInt() == 1) {
                 mBitmapBundle = in.readBundle(getClass().getClassLoader());
-                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
-                mInsets = in.readParcelable(Insets.class.getClassLoader());
+                mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader(), android.graphics.Rect.class);
+                mInsets = in.readParcelable(Insets.class.getClassLoader(), android.graphics.Insets.class);
                 mTaskId = in.readInt();
                 mUserId = in.readInt();
-                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader());
+                mTopComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
             }
         }
 
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index e6deada..78bb53d 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -871,6 +871,10 @@
             if (newGroup == null) {
                 newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
                 mAddedGroups.add(newGroup);
+            } else if (newGroup.getParent() != mMessagingLinearLayout) {
+                throw new IllegalStateException(
+                        "group parent was " + newGroup.getParent() + " but expected "
+                                + mMessagingLinearLayout);
             }
             newGroup.setImageDisplayLocation(mIsCollapsed
                     ? IMAGE_DISPLAY_LOCATION_EXTERNAL
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index f30b844..9e06e33 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -32,7 +32,6 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
-import android.util.Pools;
 import android.util.TypedValue;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -57,8 +56,8 @@
  */
 @RemoteViews.RemoteView
 public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
-    private static Pools.SimplePool<MessagingGroup> sInstancePool
-            = new Pools.SynchronizedPool<>(10);
+    private static final MessagingPool<MessagingGroup> sInstancePool =
+            new MessagingPool<>(10);
 
     /**
      * Images are displayed inline.
@@ -338,7 +337,7 @@
     }
 
     public static void dropCache() {
-        sInstancePool = new Pools.SynchronizedPool<>(10);
+        sInstancePool.clear();
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
index 27689d4..f7955c3 100644
--- a/core/java/com/android/internal/widget/MessagingImageMessage.java
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -28,7 +28,6 @@
 import android.net.Uri;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Pools;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
 import android.widget.ImageView;
@@ -44,8 +43,8 @@
 @RemoteViews.RemoteView
 public class MessagingImageMessage extends ImageView implements MessagingMessage {
     private static final String TAG = "MessagingImageMessage";
-    private static Pools.SimplePool<MessagingImageMessage> sInstancePool
-            = new Pools.SynchronizedPool<>(10);
+    private static final MessagingPool<MessagingImageMessage> sInstancePool =
+            new MessagingPool<>(10);
     private final MessagingMessageState mState = new MessagingMessageState(this);
     private final int mMinImageHeight;
     private final Path mPath = new Path();
@@ -194,7 +193,7 @@
     }
 
     public static void dropCache() {
-        sInstancePool = new Pools.SynchronizedPool<>(10);
+        sInstancePool.clear();
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index e1602a9..21ca196 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -419,6 +419,10 @@
             if (newGroup == null) {
                 newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
                 mAddedGroups.add(newGroup);
+            } else if (newGroup.getParent() != mMessagingLinearLayout) {
+                throw new IllegalStateException(
+                        "group parent was " + newGroup.getParent() + " but expected "
+                                + mMessagingLinearLayout);
             }
             newGroup.setImageDisplayLocation(mIsCollapsed
                     ? IMAGE_DISPLAY_LOCATION_EXTERNAL
diff --git a/core/java/com/android/internal/widget/MessagingPool.java b/core/java/com/android/internal/widget/MessagingPool.java
new file mode 100644
index 0000000..9c0fe4b
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingPool.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.util.Log;
+import android.util.Pools;
+import android.view.View;
+
+/**
+ * A trivial wrapper around Pools.SynchronizedPool which allows clearing the pool, as well as
+ * disabling the pool class altogether.
+ * @param <T> the type of object in the pool
+ */
+public class MessagingPool<T extends View> implements Pools.Pool<T> {
+    private static final boolean ENABLED = false;  // disabled to test b/208508846
+    private static final String TAG = "MessagingPool";
+    private final int mMaxPoolSize;
+    private Pools.SynchronizedPool<T> mCurrentPool;
+
+    public MessagingPool(int maxPoolSize) {
+        mMaxPoolSize = maxPoolSize;
+        if (ENABLED) {
+            mCurrentPool = new Pools.SynchronizedPool<>(mMaxPoolSize);
+        }
+    }
+
+    @Override
+    public T acquire() {
+        if (!ENABLED) {
+            return null;
+        }
+        T instance = mCurrentPool.acquire();
+        if (instance.getParent() != null) {
+            Log.wtf(TAG, "acquired " + instance + " with parent " + instance.getParent());
+            return null;
+        }
+        return instance;
+    }
+
+    @Override
+    public boolean release(T instance) {
+        if (instance.getParent() != null) {
+            Log.wtf(TAG, "releasing " + instance + " with parent " + instance.getParent());
+            return false;
+        }
+        if (!ENABLED) {
+            return false;
+        }
+        return mCurrentPool.release(instance);
+    }
+
+    /** Clear the pool */
+    public void clear() {
+        if (ENABLED) {
+            mCurrentPool = new Pools.SynchronizedPool<>(mMaxPoolSize);
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
index d778c59..19791db 100644
--- a/core/java/com/android/internal/widget/MessagingTextMessage.java
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.text.Layout;
 import android.util.AttributeSet;
-import android.util.Pools;
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
@@ -36,8 +35,8 @@
 @RemoteViews.RemoteView
 public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
 
-    private static Pools.SimplePool<MessagingTextMessage> sInstancePool
-            = new Pools.SynchronizedPool<>(20);
+    private static final MessagingPool<MessagingTextMessage> sInstancePool =
+            new MessagingPool<>(20);
     private final MessagingMessageState mState = new MessagingMessageState(this);
 
     public MessagingTextMessage(@NonNull Context context) {
@@ -92,7 +91,7 @@
     }
 
     public static void dropCache() {
-        sInstancePool = new Pools.SynchronizedPool<>(10);
+        sInstancePool.clear();
     }
 
     @Override
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index be5dc00..d66f461 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -870,7 +870,8 @@
                                 boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT;
                                 boolean allowedMaxSdk =
                                         maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT;
-                                if (allowedMinSdk && allowedMaxSdk) {
+                                final boolean exists = new File(lfile).exists();
+                                if (allowedMinSdk && allowedMaxSdk && exists) {
                                     int bcpSince = XmlUtils.readIntAttribute(parser,
                                             "on-bootclasspath-since", 0);
                                     int bcpBefore = XmlUtils.readIntAttribute(parser,
@@ -880,6 +881,19 @@
                                                     ? new String[0] : ldependency.split(":"),
                                             bcpSince, bcpBefore);
                                     mSharedLibraries.put(lname, entry);
+                                } else {
+                                    final StringBuilder msg = new StringBuilder(
+                                            "Ignore shared library ").append(lname).append(":");
+                                    if (!allowedMinSdk) {
+                                        msg.append(" min-device-sdk=").append(minDeviceSdk);
+                                    }
+                                    if (!allowedMaxSdk) {
+                                        msg.append(" max-device-sdk=").append(maxDeviceSdk);
+                                    }
+                                    if (!exists) {
+                                        msg.append(" ").append(lfile).append(" does not exist");
+                                    }
+                                    Slog.i(TAG, msg.toString());
                                 }
                             }
                         } else {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1a1a8ba..da62863 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -210,8 +210,8 @@
                 "com_android_internal_content_om_OverlayConfig.cpp",
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
-                "com_android_internal_os_DmabufInfoReader.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
+                "com_android_internal_os_KernelAllocationStats.cpp",
                 "com_android_internal_os_KernelCpuBpfTracking.cpp",
                 "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp",
                 "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 18e85b6..04fafb4 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -197,8 +197,8 @@
 extern int register_com_android_internal_content_om_OverlayConfig(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_DmabufInfoReader(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelAllocationStats(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env);
 extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
@@ -1655,8 +1655,8 @@
         REG_JNI(register_android_security_Scrypt),
         REG_JNI(register_com_android_internal_content_F2fsUtils),
         REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
-        REG_JNI(register_com_android_internal_os_DmabufInfoReader),
         REG_JNI(register_com_android_internal_os_FuseAppLoop),
+        REG_JNI(register_com_android_internal_os_KernelAllocationStats),
         REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking),
         REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader),
         REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index a059dd6..78e5adc 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -30,21 +30,19 @@
 
 namespace android {
 
-static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
-                          jlong width, jlong height, jint format) {
-    String8 str8;
-    if (jName) {
-        const jchar* str16 = env->GetStringCritical(jName, nullptr);
-        if (str16) {
-            str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName));
-            env->ReleaseStringCritical(jName, str16);
-            str16 = nullptr;
-        }
-    }
-    std::string name = str8.string();
+static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) {
+    ScopedUtfChars name(env, jName);
+    sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str());
+    queue->incStrong((void*)nativeCreate);
+    return reinterpret_cast<jlong>(queue.get());
+}
+
+static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
+                                   jlong width, jlong height, jint format) {
+    ScopedUtfChars name(env, jName);
     sp<BLASTBufferQueue> queue =
-            new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
-                                 height, format);
+            new BLASTBufferQueue(name.c_str(), reinterpret_cast<SurfaceControl*>(surfaceControl),
+                                 width, height, format);
     queue->incStrong((void*)nativeCreate);
     return reinterpret_cast<jlong>(queue.get());
 }
@@ -96,7 +94,8 @@
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
         // clang-format off
-        {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
+        {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate},
+        {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate},
         {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
         {"nativeDestroy", "(J)V", (void*)nativeDestroy},
         {"nativeSetSyncTransaction", "(JJZ)V", (void*)nativeSetSyncTransaction},
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index f1ae268..423ef7c 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -58,7 +58,7 @@
     jmethodID setSystemUsage;
     jmethodID setInternalCapturePreset;
     jmethodID setContentType;
-    jmethodID setFlags;
+    jmethodID replaceFlags;
     jmethodID addTag;
 } gAudioAttributesBuilderMethods;
 
@@ -130,7 +130,7 @@
                           gAudioAttributesBuilderMethods.setContentType,
                           attributes.content_type);
     env->CallObjectMethod(jAttributeBuilder.get(),
-                          gAudioAttributesBuilderMethods.setFlags,
+                          gAudioAttributesBuilderMethods.replaceFlags,
                           attributes.flags);
     env->CallObjectMethod(jAttributeBuilder.get(),
                           gAudioAttributesBuilderMethods.addTag,
@@ -205,8 +205,8 @@
     gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie(
                 env, audioAttributesBuilderClass, "setContentType",
                 "(I)Landroid/media/AudioAttributes$Builder;");
-    gAudioAttributesBuilderMethods.setFlags = GetMethodIDOrDie(
-                env, audioAttributesBuilderClass, "setFlags",
+    gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie(
+                env, audioAttributesBuilderClass, "replaceFlags",
                 "(I)Landroid/media/AudioAttributes$Builder;");
     gAudioAttributesBuilderMethods.addTag = GetMethodIDOrDie(
                 env, audioAttributesBuilderClass, "addTag",
diff --git a/core/jni/android_os_Trace.cpp b/core/jni/android_os_Trace.cpp
index f67007c..85fd5d9 100644
--- a/core/jni/android_os_Trace.cpp
+++ b/core/jni/android_os_Trace.cpp
@@ -90,6 +90,22 @@
     atrace_set_tracing_enabled(enabled);
 }
 
+static void android_os_Trace_nativeInstant(JNIEnv* env, jclass,
+        jlong tag, jstring nameStr) {
+    withString(env, nameStr, [tag](char* str) {
+        atrace_instant(tag, str);
+    });
+}
+
+static void android_os_Trace_nativeInstantForTrack(JNIEnv* env, jclass,
+        jlong tag, jstring trackStr, jstring nameStr) {
+    withString(env, trackStr, [env, tag, nameStr](char* track) {
+        withString(env, nameStr, [tag, track](char* name) {
+            atrace_instant_for_track(tag, track, name);
+        });
+    });
+}
+
 static const JNINativeMethod gTraceMethods[] = {
     /* name, signature, funcPtr */
     { "nativeSetAppTracingAllowed",
@@ -116,6 +132,12 @@
     { "nativeAsyncTraceEnd",
             "(JLjava/lang/String;I)V",
             (void*)android_os_Trace_nativeAsyncTraceEnd },
+    { "nativeInstant",
+            "(JLjava/lang/String;)V",
+            (void*)android_os_Trace_nativeInstant },
+    { "nativeInstantForTrack",
+            "(JLjava/lang/String;Ljava/lang/String;)V",
+            (void*)android_os_Trace_nativeInstantForTrack },
 
     // ----------- @CriticalNative  ----------------
     { "nativeGetEnabledTags",
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 86d7810..8c23b21 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -881,6 +881,12 @@
   return static_cast<jint>(bag->entry_count);
 }
 
+static jint NativeGetParentThemeIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const auto parentThemeResId = assetmanager->GetParentThemeResourceId(resid);
+  return parentThemeResId.value_or(0);
+}
+
 static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name,
                                         jstring def_type, jstring def_package) {
   ScopedUtfChars name_utf8(env, name);
@@ -1464,6 +1470,8 @@
     {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
     {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
     {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+    {"nativeGetParentThemeIdentifier", "(JI)I",
+     (void*)NativeGetParentThemeIdentifier},
 
     // AssetManager resource name/ID methods.
     {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index b910d16..74bbd7b 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -269,8 +269,7 @@
     return RegisterMethodsOrDie(env, kInputQueuePathName, g_methods, NELEM(g_methods));
 }
 
-AInputQueue* android_view_InputQueue_getNativePtr(jobject inputQueue) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
+AInputQueue* android_view_InputQueue_getNativePtr(JNIEnv* env, jobject inputQueue) {
     jlong ptr = env->CallLongMethod(inputQueue, gInputQueueClassInfo.getNativePtr);
     return reinterpret_cast<AInputQueue*>(ptr);
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index fb5fded..67d0c52 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -597,6 +597,14 @@
     transaction->setPosition(ctrl, x, y);
 }
 
+static void nativeSetScale(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
+                           jfloat xScale, jfloat yScale) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setMatrix(ctrl, xScale, 0, 0, yScale);
+}
+
 static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
         jobject sourceObj, jobject dstObj, jlong orientation) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -620,9 +628,19 @@
                             jobject bufferObject) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
     SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
-    sp<GraphicBuffer> buffer(
-            android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject));
-    transaction->setBuffer(ctrl, buffer);
+    sp<GraphicBuffer> graphicBuffer(GraphicBuffer::fromAHardwareBuffer(
+            android_hardware_HardwareBuffer_getNativeHardwareBuffer(env, bufferObject)));
+    transaction->setBuffer(ctrl, graphicBuffer);
+}
+
+static void nativeSetBufferTransform(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                     jlong nativeObject, jint transform) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    transaction->setTransform(ctrl, transform);
+    bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
+            NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+    transaction->setTransformToDisplayInverse(ctrl, transformToInverseDisplay);
 }
 
 static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
@@ -740,6 +758,37 @@
     }
 }
 
+static void nativeSetDamageRegion(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                  jlong nativeObject, jobject regionObj) {
+    SurfaceControl* const surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    if (regionObj == nullptr) {
+        transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+        return;
+    }
+
+    graphics::RegionIterator iterator(env, regionObj);
+    if (!iterator.isValid()) {
+        transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+        return;
+    }
+
+    Region region;
+    while (!iterator.isDone()) {
+        ARect rect = iterator.getRect();
+        region.orSelf(static_cast<const Rect&>(rect));
+        iterator.next();
+    }
+
+    if (region.getBounds().isEmpty()) {
+        transaction->setSurfaceDamageRegion(surfaceControl, Region::INVALID_REGION);
+        return;
+    }
+
+    transaction->setSurfaceDamageRegion(surfaceControl, region);
+}
+
 static void nativeSetAlpha(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jfloat alpha) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1905,10 +1954,14 @@
             (void*)nativeSetRelativeLayer },
     {"nativeSetPosition", "(JJFF)V",
             (void*)nativeSetPosition },
+    {"nativeSetScale", "(JJFF)V",
+            (void*)nativeSetScale },
     {"nativeSetSize", "(JJII)V",
             (void*)nativeSetSize },
     {"nativeSetTransparentRegionHint", "(JJLandroid/graphics/Region;)V",
             (void*)nativeSetTransparentRegionHint },
+    { "nativeSetDamageRegion", "(JJLandroid/graphics/Region;)V",
+            (void*)nativeSetDamageRegion },
     {"nativeSetAlpha", "(JJF)V",
             (void*)nativeSetAlpha },
     {"nativeSetColor", "(JJ[F)V",
@@ -2018,8 +2071,9 @@
             (void*)nativeGetDisplayedContentSample },
     {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V",
             (void*)nativeSetGeometry },
-    {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V",
+    {"nativeSetBuffer", "(JJLandroid/hardware/HardwareBuffer;)V",
             (void*)nativeSetBuffer },
+    {"nativeSetBufferTransform", "(JJI)V", (void*) nativeSetBufferTransform},
     {"nativeSetColorSpace", "(JJI)V",
             (void*)nativeSetColorSpace },
     {"nativeSyncInputWindows", "(J)V",
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index b20addb4..1381de5 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -16,8 +16,10 @@
 
 #define LOG_TAG "WindowInfosListener"
 
+#include <android/graphics/matrix.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
+#include <gui/DisplayInfo.h>
 #include <gui/SurfaceComposerClient.h>
 #include <nativehelper/JNIHelp.h>
 #include <utils/Log.h>
@@ -37,14 +39,30 @@
     jmethodID onWindowInfosChanged;
 } gListenerClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gDisplayInfoClassInfo;
+
 static jclass gInputWindowHandleClass;
 
+jobject fromDisplayInfo(JNIEnv* env, gui::DisplayInfo displayInfo) {
+    float transformValues[9];
+    for (int i = 0; i < 9; i++) {
+        transformValues[i] = displayInfo.transform[i % 3][i / 3];
+    }
+    ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues));
+    return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor,
+                          displayInfo.displayId, displayInfo.logicalWidth,
+                          displayInfo.logicalHeight, matrixObj.get());
+}
+
 struct WindowInfosListener : public gui::WindowInfosListener {
     WindowInfosListener(JNIEnv* env, jobject listener)
           : mListener(env->NewWeakGlobalRef(listener)) {}
 
     void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
-                              const std::vector<DisplayInfo>& /*displayInfos*/) override {
+                              const std::vector<DisplayInfo>& displayInfos) override {
         JNIEnv* env = AndroidRuntime::getJNIEnv();
         LOG_ALWAYS_FATAL_IF(env == nullptr, "Unable to retrieve JNIEnv in onWindowInfoChanged.");
 
@@ -64,7 +82,15 @@
             env->SetObjectArrayElement(jWindowHandlesArray, i, jWindowHandle.get());
         }
 
-        env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray);
+        jobjectArray jDisplayInfoArray =
+                env->NewObjectArray(displayInfos.size(), gDisplayInfoClassInfo.clazz, nullptr);
+        for (int i = 0; i < displayInfos.size(); i++) {
+            ScopedLocalRef<jobject> jDisplayInfo(env, fromDisplayInfo(env, displayInfos[i]));
+            env->SetObjectArrayElement(jDisplayInfoArray, i, jDisplayInfo.get());
+        }
+
+        env->CallVoidMethod(listener, gListenerClassInfo.onWindowInfosChanged, jWindowHandlesArray,
+                            jDisplayInfoArray);
         env->DeleteGlobalRef(listener);
 
         if (env->ExceptionCheck()) {
@@ -126,10 +152,16 @@
     gListenerClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
     gListenerClassInfo.onWindowInfosChanged =
             env->GetMethodID(gListenerClassInfo.clazz, "onWindowInfosChanged",
-                             "([Landroid/view/InputWindowHandle;)V");
+                             "([Landroid/view/InputWindowHandle;[Landroid/window/"
+                             "WindowInfosListener$DisplayInfo;)V");
 
     clazz = env->FindClass("android/view/InputWindowHandle");
     gInputWindowHandleClass = MakeGlobalRefOrDie(env, clazz);
+
+    clazz = env->FindClass("android/window/WindowInfosListener$DisplayInfo");
+    gDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
+    gDisplayInfoClassInfo.ctor = env->GetMethodID(gDisplayInfoClassInfo.clazz, "<init>",
+                                                  "(IIILandroid/graphics/Matrix;)V");
     return 0;
 }
 
diff --git a/core/jni/com_android_internal_os_DmabufInfoReader.cpp b/core/jni/com_android_internal_os_DmabufInfoReader.cpp
deleted file mode 100644
index 4b0a6ac..0000000
--- a/core/jni/com_android_internal_os_DmabufInfoReader.cpp
+++ /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.
- */
-
-#include <dmabufinfo/dmabufinfo.h>
-#include "core_jni_helpers.h"
-
-namespace android {
-
-static jobject DmabufInfoReader_getProcessStats(JNIEnv *env, jobject, jint pid) {
-    std::vector<dmabufinfo::DmaBuffer> buffers;
-    if (!dmabufinfo::ReadDmaBufMapRefs(pid, &buffers)) {
-        return nullptr;
-    }
-    jint mappedSize = 0;
-    jint mappedCount = buffers.size();
-    for (const auto &buffer : buffers) {
-        mappedSize += buffer.size();
-    }
-    mappedSize /= 1024;
-
-    jint retainedSize = -1;
-    jint retainedCount = -1;
-    if (dmabufinfo::ReadDmaBufFdRefs(pid, &buffers)) {
-        retainedCount = buffers.size();
-        retainedSize = 0;
-        for (const auto &buffer : buffers) {
-            retainedSize += buffer.size();
-        }
-        retainedSize /= 1024;
-    }
-
-    jclass clazz = FindClassOrDie(env, "com/android/internal/os/DmabufInfoReader$ProcessDmabuf");
-    jmethodID constructID = GetMethodIDOrDie(env, clazz, "<init>", "(IIII)V");
-    return env->NewObject(clazz, constructID, retainedSize, retainedCount, mappedSize, mappedCount);
-}
-
-static const JNINativeMethod methods[] = {
-        {"getProcessStats", "(I)Lcom/android/internal/os/DmabufInfoReader$ProcessDmabuf;",
-         (void *)DmabufInfoReader_getProcessStats},
-};
-
-int register_com_android_internal_os_DmabufInfoReader(JNIEnv *env) {
-    return RegisterMethodsOrDie(env, "com/android/internal/os/DmabufInfoReader", methods,
-                                NELEM(methods));
-}
-
-} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelAllocationStats.cpp b/core/jni/com_android_internal_os_KernelAllocationStats.cpp
new file mode 100644
index 0000000..e0a24430
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelAllocationStats.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <dmabufinfo/dmabufinfo.h>
+#include <jni.h>
+#include <meminfo/sysmeminfo.h>
+
+#include "core_jni_helpers.h"
+
+namespace {
+static jclass gProcessDmabufClazz;
+static jmethodID gProcessDmabufCtor;
+static jclass gProcessGpuMemClazz;
+static jmethodID gProcessGpuMemCtor;
+} // namespace
+
+namespace android {
+
+static jobject KernelAllocationStats_getDmabufAllocations(JNIEnv *env, jobject, jint pid) {
+    std::vector<dmabufinfo::DmaBuffer> buffers;
+    if (!dmabufinfo::ReadDmaBufMapRefs(pid, &buffers)) {
+        return nullptr;
+    }
+    jint mappedSize = 0;
+    jint mappedCount = buffers.size();
+    for (const auto &buffer : buffers) {
+        mappedSize += buffer.size();
+    }
+    mappedSize /= 1024;
+
+    jint retainedSize = -1;
+    jint retainedCount = -1;
+    if (dmabufinfo::ReadDmaBufFdRefs(pid, &buffers)) {
+        retainedCount = buffers.size();
+        retainedSize = 0;
+        for (const auto &buffer : buffers) {
+            retainedSize += buffer.size();
+        }
+        retainedSize /= 1024;
+    }
+    return env->NewObject(gProcessDmabufClazz, gProcessDmabufCtor, retainedSize, retainedCount,
+                          mappedSize, mappedCount);
+}
+
+static jobject KernelAllocationStats_getGpuAllocations(JNIEnv *env) {
+    std::unordered_map<uint32_t, uint64_t> out;
+    meminfo::ReadPerProcessGpuMem(&out);
+    jobjectArray result = env->NewObjectArray(out.size(), gProcessGpuMemClazz, nullptr);
+    if (result == NULL) {
+        jniThrowRuntimeException(env, "Cannot create result array");
+        return nullptr;
+    }
+    int idx = 0;
+    for (const auto &entry : out) {
+        jobject pidStats =
+                env->NewObject(gProcessGpuMemClazz, gProcessGpuMemCtor, entry.first, entry.second);
+        env->SetObjectArrayElement(result, idx, pidStats);
+        env->DeleteLocalRef(pidStats);
+        ++idx;
+    }
+    return result;
+}
+
+static const JNINativeMethod methods[] = {
+        {"getDmabufAllocations", "(I)Lcom/android/internal/os/KernelAllocationStats$ProcessDmabuf;",
+         (void *)KernelAllocationStats_getDmabufAllocations},
+        {"getGpuAllocations", "()[Lcom/android/internal/os/KernelAllocationStats$ProcessGpuMem;",
+         (void *)KernelAllocationStats_getGpuAllocations},
+};
+
+int register_com_android_internal_os_KernelAllocationStats(JNIEnv *env) {
+    int res = RegisterMethodsOrDie(env, "com/android/internal/os/KernelAllocationStats", methods,
+                                   NELEM(methods));
+    jclass clazz =
+            FindClassOrDie(env, "com/android/internal/os/KernelAllocationStats$ProcessDmabuf");
+    gProcessDmabufClazz = MakeGlobalRefOrDie(env, clazz);
+    gProcessDmabufCtor = GetMethodIDOrDie(env, gProcessDmabufClazz, "<init>", "(IIII)V");
+
+    clazz = FindClassOrDie(env, "com/android/internal/os/KernelAllocationStats$ProcessGpuMem");
+    gProcessGpuMemClazz = MakeGlobalRefOrDie(env, clazz);
+    gProcessGpuMemCtor = GetMethodIDOrDie(env, gProcessGpuMemClazz, "<init>", "(II)V");
+    return res;
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 45652a7..69c4f3d 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -84,6 +84,10 @@
     return (jlong)asLongMultiStateCounter(nativePtr)->updateValue((int64_t)value, timestamp);
 }
 
+static void native_incrementValue(jlong nativePtr, jlong count, jlong timestamp) {
+    asLongMultiStateCounter(nativePtr)->incrementValue(count, timestamp);
+}
+
 static void native_addCount(jlong nativePtr, jlong count) {
     asLongMultiStateCounter(nativePtr)->addValue(count);
 }
@@ -172,6 +176,8 @@
         // @CriticalNative
         {"native_updateValue", "(JJJ)J", (void *)native_updateValue},
         // @CriticalNative
+        {"native_incrementValue", "(JJJ)V", (void *)native_incrementValue},
+        // @CriticalNative
         {"native_addCount", "(JJ)V", (void *)native_addCount},
         // @CriticalNative
         {"native_reset", "(J)V", (void *)native_reset},
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 1b3f78c..aacf700 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -124,6 +124,7 @@
 static constexpr const char* kZygoteInitClassName = "com/android/internal/os/ZygoteInit";
 static jclass gZygoteInitClass;
 static jmethodID gGetOrCreateSystemServerClassLoader;
+static jmethodID gPrefetchStandaloneSystemServerJars;
 
 static bool gIsSecurityEnforced = true;
 
@@ -1617,6 +1618,12 @@
             // at a later point (but may not have rights to use AoT artifacts).
             env->ExceptionClear();
         }
+        // Also prefetch standalone system server jars. The reason for doing this here is the same
+        // as above.
+        env->CallStaticObjectMethod(gZygoteInitClass, gPrefetchStandaloneSystemServerJars);
+        if (env->ExceptionCheck()) {
+            env->ExceptionClear();
+        }
     }
 
     if (setresgid(gid, gid, gid) == -1) {
@@ -2678,6 +2685,9 @@
   gGetOrCreateSystemServerClassLoader =
           GetStaticMethodIDOrDie(env, gZygoteInitClass, "getOrCreateSystemServerClassLoader",
                                  "()Ljava/lang/ClassLoader;");
+  gPrefetchStandaloneSystemServerJars =
+          GetStaticMethodIDOrDie(env, gZygoteInitClass, "prefetchStandaloneSystemServerJars",
+                                 "()V");
 
   RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods));
 
diff --git a/core/jni/include/android_runtime/android_view_InputQueue.h b/core/jni/include/android_runtime/android_view_InputQueue.h
index c1b611c..115e2f8 100644
--- a/core/jni/include/android_runtime/android_view_InputQueue.h
+++ b/core/jni/include/android_runtime/android_view_InputQueue.h
@@ -80,7 +80,7 @@
     Vector<key_value_pair_t<InputEvent*, bool> > mFinishedEvents;
 };
 
-extern AInputQueue* android_view_InputQueue_getNativePtr(jobject inputQueue);
+extern AInputQueue* android_view_InputQueue_getNativePtr(JNIEnv* env, jobject inputQueue);
 
 } // namespace android
 
diff --git a/core/proto/android/app/OWNERS b/core/proto/android/app/OWNERS
index cc479e6..4d76aee 100644
--- a/core/proto/android/app/OWNERS
+++ b/core/proto/android/app/OWNERS
@@ -1 +1,2 @@
-per-file location_time_zone_manager.proto, time_zone_detector.proto = nfuller@google.com, mingaleev@google.com
+per-file location_time_zone_manager.proto = file:platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
+per-file time_zone_detector.proto = file:platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b33ca1d..b52aa82 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -32,16 +32,8 @@
 }
 
 /*
- * An obfuscated and simplified time zone suggestion for metrics use.
- *
- * The suggestion's time zone IDs (which relate to location) are obfuscated by
- * mapping them to an ordinal. When the ordinal is assigned consistently across
- * several objects (i.e. so the same time zone ID is always mapped to the same
- * ordinal), this allows comparisons between those objects. For example, we can
- * answer "did these two suggestions agree?", "does the suggestion match the
- * device's current time zone?", without leaking knowledge of location. Ordinals
- * are also significantly more compact than full IANA TZDB IDs, albeit highly
- * unstable and of limited use.
+ * A generic-form time zone suggestion for metrics use. Required to be a superset of the
+ * MetricsTimeZoneSuggestion proto defined in atoms.proto to ensure binary compatibility.
  */
 message MetricsTimeZoneSuggestion {
   option (android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -55,5 +47,24 @@
   // The ordinals for time zone(s) in the suggestion. Always empty for
   // UNCERTAIN, and can be empty for CERTAIN, for example when the device is in
   // a disputed area / on an ocean.
-  repeated uint32 time_zone_ordinals = 2;
+  //
+  // The suggestion's time zone IDs (which relate to location) are obfuscated by
+  // mapping them to an ordinal. When the ordinal is assigned consistently across
+  // several objects (i.e. so the same time zone ID is always mapped to the same
+  // ordinal), this allows comparisons between those objects. For example, we can
+  // answer "did these two suggestions agree?", "does the suggestion match the
+  // device's current time zone?", without leaking knowledge of location. Ordinals
+  // are also significantly more compact than full IANA TZDB IDs, albeit unstable
+  // and of limited use.
+  repeated int32 time_zone_ordinals = 2;
+
+  // The actual time zone ID(s) in the suggestion. Similar to time_zone_ordinals
+  // but contains the actual string IDs.
+  //
+  // This information is only captured / reported for some devices based on the
+  // value of a server side flag, i.e. it could be enabled for internal testers.
+  // Therefore the list can be empty even when time_zone_ordinals is populated.
+  //
+  // When enabled, see time_zone_ordinals for the expected number of values.
+  repeated string time_zone_ids = 3;
 }
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index d52af5c..81d849e 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -78,6 +78,7 @@
         DIALOG = 3;
         SHELL = 4;
         OTHER = 5;
+        SAFETY_HUB = 6;
     }
 
     // Source for which sensor privacy was toggled.
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index db5d49d..b406578 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -672,18 +672,31 @@
     optional SettingProto new_contact_aggregator = 79 [ (android.privacy).dest = DEST_AUTOMATIC ];
     optional SettingProto night_display_forced_auto_mode_available = 80 [ (android.privacy).dest = DEST_AUTOMATIC ];
 
-    message NitzUpdate {
+    message Nitz {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
 
-        // If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment to
-        // SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
-        // exceeded.
-        optional SettingProto diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
-        // The length of time in milli-seconds that automatic small adjustments to
-        // SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
-        optional SettingProto spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        // If UTC time between two NITZ signals is greater than this value then the second signal
+        // cannot be ignored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto update_diff = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        // If the elapsed realtime between two NITZ signals is greater than this value then the
+        // second signal cannot be ignored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto update_spacing = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+        // If the device connects to a telephony network and was disconnected from a telephony
+        // network for less than this time, a previously received NITZ signal can be restored.
+        //
+        // This value is in milliseconds. It is used for telephony-based time and time zone
+        // detection.
+        optional SettingProto network_disconnect_retention = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
-    optional NitzUpdate nitz_update = 81;
+    optional Nitz nitz = 81;
 
     message Notification {
         option (android.msg_privacy).dest = DEST_EXPLICIT;
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 211f78e..23453876 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -380,6 +380,7 @@
     optional bool pip_auto_enter_enabled = 31;
     optional bool in_size_compat_mode = 32;
     optional float min_aspect_ratio = 33;
+    optional bool provides_max_bounds = 34;
 }
 
 /* represents WindowToken */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 316ea34..e2a2ac6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1210,6 +1210,14 @@
         android:description="@string/permdesc_readPhoneState"
         android:protectionLevel="dangerous" />
 
+    <!-- Allows read only access to phone state with a non dangerous permission,
+         including the information like cellular network type, software version. -->
+    <permission android:name="android.permission.READ_BASIC_PHONE_STATE"
+            android:permissionGroup="android.permission-group.UNDEFINED"
+            android:label="@string/permlab_readBasicPhoneState"
+            android:description="@string/permdesc_readBasicPhoneState"
+            android:protectionLevel="normal" />
+
     <!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
          granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
          <p>Protection level: dangerous-->
@@ -1524,6 +1532,7 @@
                 android:label="@string/permlab_postNotification"
                 android:description="@string/permdesc_postNotification"
                 android:protectionLevel="dangerous" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <!-- ====================================================================== -->
     <!-- REMOVED PERMISSIONS                                                    -->
@@ -2057,7 +2066,7 @@
     <permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Control access to email providers exclusively for Bluetooth
+    <!-- @SystemApi Control access to email providers exclusively for Bluetooth
          @hide
     -->
     <permission android:name="android.permission.BLUETOOTH_MAP"
@@ -2782,6 +2791,11 @@
     <permission android:name="android.permission.CREATE_USERS"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
+         device. -->
+    <permission android:name="android.permission.QUERY_USERS"
+                android:protectionLevel="signature|role" />
+
     <!-- Allows an application to access data blobs across users. -->
     <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
         android:protectionLevel="signature|privileged|development|role" />
@@ -2789,7 +2803,7 @@
     <!-- @SystemApi @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
     <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
-        android:protectionLevel="signature|role"
+        android:protectionLevel="signature|role|setup"
         android:label="@string/permlab_manageProfileAndDeviceOwners"
         android:description="@string/permdesc_manageProfileAndDeviceOwners" />
 
@@ -3500,7 +3514,7 @@
     <!-- Allows an application to read or write the secure system settings.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_SECURE_SETTINGS"
-        android:protectionLevel="signature|privileged|development|role" />
+        android:protectionLevel="signature|privileged|development|role|installer" />
 
     <!-- Allows an application to retrieve state dump information from system services.
     <p>Not for use by third-party applications. -->
@@ -3914,6 +3928,15 @@
     <permission android:name="android.permission.BIND_WALLPAPER"
         android:protectionLevel="signature|privileged" />
 
+
+    <!-- Must be required by a game service to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_GAME_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature
@@ -3974,6 +3997,14 @@
     <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.selectiontoolbar.SelectionToolbarRenderService,
+          to ensure that only the system can bind to it.
+          @hide This is not a third-party API (intended for OEMs and system apps).
+          <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.contentcapture.ContentCaptureService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4677,6 +4708,13 @@
     <permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
         android:protectionLevel="signature|privileged|role" />
 
+    <!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
+        call.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @TestApi Allows an application to query audio related state.
          @hide -->
     <permission android:name="android.permission.QUERY_AUDIO_STATE"
@@ -5506,10 +5544,18 @@
 
     <!-- Allows an application to interact with the currently active
         {@link com.android.server.communal.CommunalManagerService}.
-        @hide -->
+        @hide
+        @TestApi -->
     <permission android:name="android.permission.WRITE_COMMUNAL_STATE"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an application to view information from the currently active
+         {@link com.android.server.communal.CommunalManagerService}.
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.READ_COMMUNAL_STATE"
+                android:protectionLevel="signature|privileged"/>
+
     <!-- Allows the holder to manage whether the system can bind to services
          provided by instant apps. This permission is intended to protect
          test/development fucntionality and should be used only in such cases.
@@ -5798,7 +5844,7 @@
     <!-- @SystemApi Allows sensor privacy to be modified.
          @hide -->
     <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
-                android:protectionLevel="internal|role" />
+                android:protectionLevel="internal|role|installer" />
 
     <!-- @SystemApi Allows sensor privacy changes to be observed.
          @hide -->
@@ -5845,6 +5891,10 @@
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature|recents" />
+    <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
+         window to the window where the touch currently is on top of.  @hide -->
+    <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
+                android:protectionLevel="signature|recents" />
     <!--  Allows the caller to change the associations between input devices and displays.
         Very dangerous! @hide -->
     <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
@@ -6008,6 +6058,19 @@
     <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to launch device manager setup screens.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"
+        android:protectionLevel="signature|role" />
+
+    <!-- @SystemApi Allows an application to update certain device management related system
+         resources.
+         @hide -->
+    <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
+                android:protectionLevel="signature|role" />
+    
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -6509,6 +6572,16 @@
             </intent-filter>
         </service>
 
+        <!-- TODO: Move to ExtServices or relevant component. -->
+        <service android:name="com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService"
+                 android:permission="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+                 android:process=":ui"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.service.selectiontoolbar.SelectionToolbarRenderService"/>
+            </intent-filter>
+        </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 165dcad..b18a989 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -30,4 +30,7 @@
 per-file res/xml/config_user_types.xml = file:/MULTIUSER_OWNERS
 
 # Car
-per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNERS
\ No newline at end of file
+per-file res/values/dimens_car.xml = file:/platform/packages/services/Car:/OWNERS
+
+# Wear
+per-file res/*-watch/* = file:/platform/frameworks/opt/wear:/OWNERS
diff --git a/core/res/res/drawable/ic_add_supervised_user.xml b/core/res/res/drawable/ic_add_supervised_user.xml
new file mode 100644
index 0000000..a493775
--- /dev/null
+++ b/core/res/res/drawable/ic_add_supervised_user.xml
@@ -0,0 +1,34 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="40dp"
+        android:height="40dp"
+        android:viewportWidth="20"
+        android:viewportHeight="20"
+        android:tint="?android:attr/colorControlNormal">
+
+    <group
+        android:scaleX="0.5"
+        android:scaleY="0.5">
+
+        <path
+            android:fillColor="@android:color/white"
+            android:pathData="M15.625,22.5q-2.375,0 -4.063,-1.688 -1.687,-1.687 -1.687,-4.104 0,-2.375 1.688,-4.062 1.687,-1.688 4.062,-1.688 2.417,0 4.105,1.688 1.687,1.687 1.687,4.062 0,2.417 -1.688,4.105 -1.687,1.687 -4.104,1.687zM15.625,19.708q1.292,0 2.146,-0.875 0.854,-0.875 0.854,-2.125t-0.854,-2.125q-0.854,-0.875 -2.146,-0.875 -1.208,0 -2.104,0.875 -0.896,0.875 -0.896,2.125t0.896,2.125q0.896,0.875 2.104,0.875zM27.875,24.333q-1.792,0 -3.063,-1.27 -1.27,-1.271 -1.27,-3.063 0,-1.792 1.27,-3.063 1.271,-1.27 3.063,-1.27 1.833,0 3.083,1.27 1.25,1.271 1.25,3.063 0,1.792 -1.25,3.063 -1.25,1.27 -3.083,1.27zM17.458,33.667q1.959,-3.75 5.063,-5.063 3.104,-1.312 5.354,-1.312 0.958,0 1.813,0.145 0.854,0.146 1.729,0.396 1.041,-1.541 1.75,-3.583 0.708,-2.042 0.708,-4.25 0,-5.792 -4.041,-9.834Q25.791,6.125 20,6.125t-9.834,4.041Q6.125,14.208 6.125,20q0,2.042 0.563,3.938 0.562,1.895 1.604,3.437 1.625,-0.833 3.52,-1.333 1.896,-0.5 3.813,-0.5 1.208,0 2.333,0.188 1.125,0.187 2.125,0.52 -0.833,0.458 -1.562,1 -0.729,0.542 -1.354,1.125 -0.459,-0.042 -0.813,-0.042h-0.729q-1.375,0 -2.916,0.355 -1.542,0.354 -2.751,0.937 1.5,1.583 3.438,2.645 1.937,1.063 4.062,1.397zM20,36.667q-3.417,0 -6.459,-1.313 -3.041,-1.312 -5.312,-3.583 -2.271,-2.271 -3.583,-5.313Q3.333,23.418 3.333,20q0,-3.458 1.313,-6.479Q5.958,10.5 8.229,8.229t5.313,-3.583Q16.582,3.333 20,3.333q3.458,0 6.479,1.313 3.021,1.312 5.292,3.583t3.584,5.292q1.312,3.021 1.312,6.479 0,3.417 -1.313,6.459 -1.312,3.041 -3.583,5.312 -2.271,2.271 -5.292,3.584 -3.021,1.312 -6.479,1.312z"/>
+
+    </group>
+
+</vector>
+
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 90caacc..933b4d2 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -20,8 +20,10 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:layout_gravity="center"
     android:maxCollapsedHeight="0dp"
     android:maxCollapsedHeightSmall="56dp"
+    android:maxWidth="@dimen/chooser_width"
     android:id="@id/contentPanel">
 
     <RelativeLayout
diff --git a/core/res/res/layout/chooser_grid_preview_image.xml b/core/res/res/layout/chooser_grid_preview_image.xml
index 0d04d7f3..52692b0 100644
--- a/core/res/res/layout/chooser_grid_preview_image.xml
+++ b/core/res/res/layout/chooser_grid_preview_image.xml
@@ -34,7 +34,7 @@
     <view class="com.android.internal.app.ChooserActivity$RoundedRectImageView"
           android:id="@+id/content_preview_image_1_large"
           android:layout_width="120dp"
-          android:layout_height="140dp"
+          android:layout_height="104dp"
           android:layout_alignParentTop="true"
           android:adjustViewBounds="true"
           android:gravity="center"
@@ -44,7 +44,7 @@
           android:id="@+id/content_preview_image_2_large"
           android:visibility="gone"
           android:layout_width="120dp"
-          android:layout_height="140dp"
+          android:layout_height="104dp"
           android:layout_alignParentTop="true"
           android:layout_toRightOf="@id/content_preview_image_1_large"
           android:layout_marginLeft="10dp"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index c02981e..585c372 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Laat die program toe om die kitsboodskapdiens te gebruik om oproepe sonder jou ingryping te maak."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lees foonstatus en identiteit"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Laat die program toe om toegang tot die foonfunksies van die toestel te verkry. Hierdie toestemming laat die program toe om te bepaal wat die foonnommer en toestel-IDs is, of die oproep aan die gang is, en die afgeleë nommer wat deur \'n oproep verbind word."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lees basiese telefoniestatus en -identiteit"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Gee die program toegang tot die toestel se basiese telefoniekenmerke."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"roeteer oproepe deur die stelsel"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Laat die program toe om sy oproepe deur die stelsel te stuur om die oproepervaring te verbeter."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"sien en beheer oproepe deur die stelsel."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tik om jou gesigmodel uit te vee en voeg jou gesig dan weer by"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Stel Gesigslot op"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Ontsluit jou foon deur daarna te kyk"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Skakel "<b>"kameratoegang"</b>" in Instellings &gt; Privaatheid aan om Gesigslot te gebruik"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Stel meer maniere op om te ontsluit"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tik om \'n vingerafdruk by te voeg"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Vingerafdrukslot"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Berei tans <xliff:g id="APPNAME">%1$s</xliff:g> voor."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Begin programme."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Voltooi herlaai."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Skakel skerm af?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Jy het die aan/af-skakelaar gedruk terwyl jy jou vingerafdruk gestel het.\n\nDit skakel gewoonlik jou skerm af."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Skakel af"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Kanselleer"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Gaan voort met opstelling?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Jy het die aan/af-skakelaar gedruk – dit skakel gewoonlik die skerm af.\n\nProbeer liggies tik terwyl jy jou vingerafdruk opstel."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Skakel skerm af"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Doen opstelling"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Gaan voort met vingerafdrukverifiëring?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Jy het die aan/af-skakelaar gedruk – dit skakel gewoonlik die skerm af.\n\nProbeer liggies tik om jou vingerafdruk te verifieer."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Skakel skerm af"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Gaan voort"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> loop"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tik om na die speletjie terug te keer"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Kies speletjie"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index be4eeb8..f338166 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"መተግበሪያው ያለእርስዎ ጣልቃ ገብነት ጥሪዎችን ለማድረግ የአይኤምኤስ አገልግሎቱን እንዲጠቀም ያስችለዋል።"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"የስልክ ሁኔታና ማንነት አንብብ"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"መተግበሪያው የመሳሪያውን የስልክ ባህሪያት ላይ እንዲደርስ ይፈቅድለታል። ይህ ፈቃድ መተግበሪያው የስልክ ቁጥሩን እና የመሳሪያውን መታወቂያዎች፣ ጥሪ የነቃ እንደሆነ፣ እና በጥሪ የተገናኘውን የሩቅ ቁጥር እንዲወስን ይፈቅድለታል።"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"መሠረታዊ የቴሌፎኒ ሁኔታ እና ማንነት ያንብቡ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"መተግበሪያው የመሣሪያውን መሠረታዊ የቴሌፎኒ ባህሪያት እንዲደርስ ይፈቅድለታል።"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ጥሪዎችን በስርዓቱ በኩል አዙር"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"መተግበሪያው የጥሪ ተሞክሮን እንዲያሻሽል ጥሪዎቹን በስርዓቱ በኩል እንዲያዞር ያስችለዋል።"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"በሥርዓቱ በኩል ጥሪዎችን ይመልከቱ እና ይቆጣጠሩ።"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"የእርስዎ የመልክ ሞዴል ለመሰረዝ መታ ያድርጉ፣ ከዚያ መልክዎን እንደገና ያክሉ"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"በመልክ መክፈትን ያዋቅሩ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ስልክዎን በመመልከት ያስከፍቱት"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"በመልክ መክፈትን ለመጠቀም "<b>"የካሜራ መዳረሻ"</b>"ን በቅንብሮች እና ግላዊነት ውስጥ ያብሩ"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"የሚከፍቱባቸው ተጨማሪ መንገዶችን ያቀናብሩ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"የጣት አሻራን ለማከል መታ ያድርጉ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"በጣት አሻራ መክፈቻ"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g>ን ማዘጋጀት።"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"መተግበሪያዎችን በማስጀመር ላይ፡፡"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"አጨራረስ ማስነሻ፡፡"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ማያ ገጽ ይጥፋ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"የጣት አሻራዎን ሲያዋቅሩ የኃይል አዝራሩን ተጫንተውታል። \n\n ይህ አብዛኛው ጊዜ ማያ ገጽዎን ያጠፈዋል።"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"አጥፋ"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ይቅር"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ማዋቀር ይቀጥሉ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"የማብሪያ/ማጥፊያ ቁልፉን ተጭነዋል — ይህ ብዙውን ጊዜ ማያ ገጹን ያጠፋል።\n\nየጣት አሻራዎን በሚያዋቅሩበት ጊዜ በትንሹ መታ ለማድረግ ይሞክሩ።"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ማያ ገጽን አጥፋ"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ማዋቀር ቀጥል"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ቀጥል"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> አሂድ"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ወደ ጨዋታ ለመመለስ መታ ያድርጉ"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ጨዋታ ይምረጡ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 0748af2..c15f298 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -483,6 +483,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"للسماح للتطبيق باستخدام خدمة الرسائل الفورية لإجراء المكالمات بدون تدخل منك."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"قراءة حالة الهاتف والهوية"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"للسماح للتطبيق بالدخول إلى ميزات الهاتف في الجهاز. ويتيح هذا الإذن للتطبيق تحديد رقم الهاتف ومعرّفات الجهاز، وما إذا كانت هناك مكالمة نشطة والرقم البعيد الذي تم الاتصال به في المكالمة."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"قراءة حالة وهوية الاتصال الهاتفي الأساسيتين"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"يسمح هذا الإذن للتطبيق بالوصول إلى ميزات الاتصال الهاتفي الأساسية للجهاز."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"توجيه المكالمات من خلال النظام"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"يسمح للتطبيق بتوجيه المكالمات من خلال النظام لتحسين تجربة الاتصال."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"رؤية المكالمات والتحكّم فيها من خلال النظام"</string>
@@ -634,6 +636,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"انقر لحذف نموذج الوجه ثم أضِف نموذجًا لوجهك مرة أخرى."</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"إعداد ميزة \"فتح الجهاز بالتعرف على الوجه\""</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"يمكنك فتح قفل هاتفك بمجرّد النظر إلى الشاشة."</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"‏لاستخدام ميزة \"فتح الجهاز بالتعرف على الوجه\"، عليك منح إذن "<b>"الوصول إلى الكاميرا"</b>" في الإعدادات &gt; الخصوصية."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"إعداد المزيد من الطرق لفتح قفل الجهاز"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"انقر لإضافة بصمة إصبع."</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"فتح الجهاز ببصمة الإصبع"</string>
@@ -1356,10 +1359,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"جارٍ تحضير <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"بدء التطبيقات."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"جارٍ إعادة التشغيل."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"هل تريد إيقاف الشاشة؟"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"أثناء إعداد بصمة الإصبع، ضغطت على زر التشغيل.\n\nيؤدي هذا الإجراء عادةً إلى إيقاف الشاشة."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"إيقاف"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"إلغاء"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"هل تريد مواصلة عملية الإعداد؟"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ضغطت على زر التشغيل، يؤدي هذا عادةً إلى إيقاف الشاشة.\n\nجرِّب النقر بخفة أثناء إعداد بصمتك."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"إيقاف الشاشة"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"مواصلة عملية الإعداد"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"متابعة"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> يعمل"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"انقر للعودة إلى اللعبة"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"اختيار اللعبة"</string>
@@ -1727,7 +1734,7 @@
     <string name="default_audio_route_category_name" msgid="5241740395748134483">"النظام"</string>
     <string name="bluetooth_a2dp_audio_route_name" msgid="4214648773120426288">"صوت بلوتوث"</string>
     <string name="wireless_display_route_description" msgid="8297563323032966831">"عرض شاشة لاسلكي"</string>
-    <string name="media_route_button_content_description" msgid="2299223698196869956">"إرسال"</string>
+    <string name="media_route_button_content_description" msgid="2299223698196869956">"البث"</string>
     <string name="media_route_chooser_title" msgid="6646594924991269208">"الاتصال بجهاز"</string>
     <string name="media_route_chooser_title_for_remote_display" msgid="3105906508794326446">"بث الشاشة على الجهاز"</string>
     <string name="media_route_chooser_searching" msgid="6119673534251329535">"جارٍ البحث عن الأجهزة…"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 4cfb88c..9dbfb2e 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"আপোনাৰ হস্তক্ষেপৰ অবিহনে আইএমএছ সেৱা ব্যৱহাৰ কৰি কল কৰিবলৈ এপক অনুমতি দিয়ে।"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ফ\'নৰ স্থিতি আৰু পৰিচয় পঢ়ক"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ডিভাইচত থকা ফ\'নৰ সুবিধাসমূহ ব্য়ৱহাৰ কৰিবলৈ এপটোক অনুমতি দিয়ে৷ এই অনুমতিয়ে কোনো কল সক্ৰিয় হৈ থাককেই বা নাথাকক আৰু দূৰবৰ্তী নম্বৰটো কলৰ দ্বাৰা সংযোজিত হওকেই বা নহওক এপটোক ফ\'ন নম্বৰ আৰু ডিভাইচৰ পৰিচয় নিৰ্ধাৰণ কৰিবলৈ অনুমতি দিয়ে৷"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"প্ৰাথমিক টেলিফ\'নী স্থিতি আৰু পৰিচয় পঢ়ক"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"এপ্‌টোক ডিভাইচটোৰ প্ৰাথমিক টেলিফ’নী সুবিধাসমূহ এক্সেছ কৰাৰ অনুমতি দিয়ে।"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ছিষ্টেমৰ জৰিয়তে কল কৰিব পাৰে"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল কৰাৰ অভিজ্ঞতাক উন্নত কৰিবলৈ এপটোক ছিষ্টেমৰ জৰিয়তে কলসমূহ কৰিবলৈ দিয়ে।"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ছিষ্টেমৰ জৰিয়তে কলবোৰ চোৱা আৰু নিয়ন্ত্ৰণ কৰা।"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"আপোনাৰ মুখাৱয়বৰ মডেলটো মচিবলৈ টিপক, তাৰ পাছত পুনৰ আপোনাৰ মুখাৱয়ব যোগ দিয়ক"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ফেচ আনলক সুবিধাটো ছেট আপ কৰক"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"আপোনাৰ ফ’নটোলৈ চাই সেইটো আনলক কৰক"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ফেচ আনলক সুবিধাটো ব্যৱহাৰ কৰিবলৈ ছেটিং &gt; গোপনীয়তাত "<b>"কেমেৰাৰ এক্সেছ"</b>" অন কৰক"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"আনলক কৰাৰ অধিক উপায় ছেট আপ কৰক"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"এটা ফিংগাৰপ্ৰিণ্ট যোগ দিবলৈ টিপক"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ফিংগাৰপ্ৰিন্ট আনলক"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g>সাজু কৰি থকা হৈছে।"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"আৰম্ভ হৈ থকা এপসমূহ।"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"বুট কাৰ্য সমাপ্ত কৰিছে।"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"স্ক্ৰীন অফ কৰিবনে?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"আপোনাৰ ফিংগাৰপ্ৰিণ্ট ছেট আপ কৰাৰ সময়ত, আপুনি পাৱাৰ বুটামটো টিপিছে।\n\nএইটোৱে সচৰাচৰ আপোনাৰ স্ক্ৰীনখন অফ কৰে।"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"অফ কৰক"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"বাতিল কৰক"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ছেট আপ অব্যাহত ৰাখিবনে?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"আপুনি পাৱাৰ বুটামটো টিপিছে — এইটোৱে সাধাৰণতে স্ক্ৰীনখন অফ কৰে।\n\nআপোনাৰ ফিংগাৰপ্ৰিণ্টটো ছেট আপ কৰাৰ সময়ত লাহেকৈ টিপি চাওক।"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"স্ক্ৰীন অফ কৰক"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ছেট আপ অব্যাহত ৰাখক"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"অব্যাহত ৰাখক"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> চলি আছে"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"গেইমলৈ উভতি যাওক"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"গেইম বাছনি কৰক"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 136563a..1c93552 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Tətbiqə müdaxilə olmadan zəng etmək üçün IMS xidmətindən istifadə etməyə imkan verir."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"telefon statusunu və identifikasiyanı oxuyur"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Tətbiqə cihazın telefon funksiyalarına giriş icazəsi verir. Belə icazəli tətbiq bu telefonun nömrəsini və cihaz İD\'ni, zəngin aktiv olub-olmadığını və zəng edilən nömrəni müəyyən edə bilər."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"əsas telefoniya statusu və identifikasiyasını oxuyun"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Tətbiqə cihazın əsas telefoniya funksiyalarına giriş etmək imkanı verir."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"zəngləri sistem üzərindən yönləndirin"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Tətbiqə, zəng təcrübəsini yaxşılaşdırmaq üçün, zəngləri sistem üzərindən yönləndirməyə icazə verilir."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"zənglərə sistemdə baxın və nəzarət edin."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Üz modelinizi silmək üçün toxunun, sonra yenidən üzünüzü əlavə edin"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Üz ilə kiliddən çıxarmanı ayarlayın"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Telefona baxaraq onu kiliddən çıxarın"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Üz ilə Kiliddən Açma funksiyasını istifadə etmək üçün Ayarlar &gt; Məxfilik bölməsində "<b>"Kameraya girişi"</b>" aktiv edin"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Kiliddən çıxarmağın daha çox yolunu ayarlayın"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Barmaq izi əlavə etmək üçün toxunun"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Barmaq izi ilə kiliddən çıxarma"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> proqramının hazırlanması."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Tətbiqlər başladılır."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Yükləmə başa çatır."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Ekran deaktiv edilsin?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Barmaq izinizi ayarlayarkən Qidalanma düyməsinə basdınız.\n\nBu, adətən ekranınızı deaktiv edir."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Deaktiv edin"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Ləğv edin"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Ayarlamağa davam edilsin?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Qidalanma düyməsini basdınız — adətən bu, ekranı söndürür.\n\nBarmaq izini ayarlayarkən yüngülcə toxunmağa çalışın."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Ekranı söndürün"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Ayarlamağa davam edin"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Barmaq izini doğrulamağa davam edilsin?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Qidalanma düyməsini basdınız — adətən bu, ekranı söndürür.\n\nBarmaq izini doğrulamaq üçün yüngülcə toxunmağa çalışın."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Ekranı söndürün"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Davam edin"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> çalışır"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Oyuna qayıtmaq üçün klikləyin"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Oyun seçin"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 00fc2b5..9241631 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -474,6 +474,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Dozvoljava aplikaciji da koristi uslugu razmene trenutnih poruka da bi upućivala pozive bez vaše intervencije."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"čitanje statusa i identiteta telefona"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Dozvoljava aplikaciji da pristupa funkcijama telefona na uređaju. Ova dozvola omogućava aplikaciji da utvrdi broj telefona i ID-ove uređaja, zatim da li je poziv aktivan, kao i broj daljinskog uređaja sa kojim je uspostavljen poziv."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"očitavanje osnovnog telefonskog statusa i identiteta"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Omogućava aplikaciji da pristupa osnovnim telefonskim funkcijama uređaja."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"preusmeravanje poziva preko sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Dozvoljava aplikaciji da preusmerava pozive preko sistema da bi poboljšala doživljaj pozivanja."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"pregled i kontrola poziva preko sistema."</string>
@@ -625,6 +627,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Dodirnite da biste izbrisali model lica, pa ponovo dodajte svoje lice"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Podesite otključavanje licem"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Otključajte telefon tako što ćete ga pogledati"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Da biste koristili otključavanje licem, uključite "<b>"pristup kameri"</b>" u odeljku Podešavanja &gt; Privatnost"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Podesite još načina za otključavanje"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Dodirnite da biste dodali otisak prsta"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Otključavanje otiskom prsta"</string>
@@ -1296,10 +1299,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Priprema se <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Pokretanje aplikacija."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Završavanje pokretanja."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Želite da isključite ekran?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Pritisli ste dugme za uključivanje tokom podešavanja otiska prsta.\n\nTako se najčešće isključuje ekran."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Isključi"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Otkaži"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Želite li da nastavite sa podešavanjem?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pritisnuli ste dugme za uključivanje – time obično isključujete ekran.\n\nProbajte lagano da dodirnete dok podešavate otisak prsta."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Isključi ekran"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Nastavi podešavanje"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Nastavljate verifikaciju otiska prsta?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pritisnuli ste dugme za uključivanje – time obično isključujete ekran.\n\nProbajte lagano da dodirnete da biste verifikovali otisak prsta."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Isključi ekran"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Nastavi"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> je pokrenuta"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Dodirnite da biste se vratili u igru"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Odaberite igru"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 0157e66..849d53b 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -404,7 +404,7 @@
     <string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"Дазваляе прыкладанню захоўваць некаторыя пастаянныя часткi ў памяцi. Гэта можа абмежаваць аб\'ём памяці, даступнай для іншых прыкладанняў, i запаволiць працу планшэта."</string>
     <string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"Дазваляе праграме пастаянна захоўваць некаторыя свае часткі ў памяці прылады. Гэта можа абмежаваць аб\'ём памяці, даступнай для іншых праграм, і запаволіць працу прылады Android TV."</string>
     <string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"Дазваляе прыкладанню захоўваць некаторыя пастаянныя часткi ў памяцi. Гэта можа абмежаваць аб\'ём памяці, даступнай для іншых прыкладанняў, i запаволiць працу тэлефона."</string>
-    <string name="permlab_foregroundService" msgid="1768855976818467491">"запусціць асноўныя сэрвісы"</string>
+    <string name="permlab_foregroundService" msgid="1768855976818467491">"запусціць актыўныя сэрвісы"</string>
     <string name="permdesc_foregroundService" msgid="8720071450020922795">"Дазваляе праграме выкарыстоўваць асноўныя сэрвісы."</string>
     <string name="permlab_getPackageSize" msgid="375391550792886641">"вымерыць прастору для захоўвання прыкладання"</string>
     <string name="permdesc_getPackageSize" msgid="742743530909966782">"Дазваляе прыкладанням атрымліваць яго код, дадзеныя і аб\'ём кэш-памяці"</string>
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Дазваляе праграмам выкарыстоўваць службу IMS, каб рабіць выклікі без вашага ўмяшання."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"чытанне статусу тэлефона і ідэнтыфікацыя"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Дазваляе прыкладанням атрымлiваць доступ да функцый тэлефона на прыладзе. Дзякуючы гэтаму дазволу прыкладанне можа вызначаць iдэнтыфiкатары нумару тэлефона i прылады, незалежна ад таго, цi актыўны выклiк, i аддалены нумар, на якi робiцца выклiк."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"счытваць даныя пра асноўны стан тэлефаніі і ідэнтыфікацыю"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Дазваляе праграме мець доступ да асноўных функцый тэлефаніі на прыладзе."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"перанакіраванне выклікаў праз сістэму"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Дазваляе праграме перанакіроўваць выклікі праз сістэму ў мэтах паляпшэння выклікаў."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"праглядаць выклікі і кіраваць імі праз сістэму."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Націсніце, каб выдаліць мадэль твару, пасля дадайце твар яшчэ раз"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Наладзьце распазнаванне твару"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Разблакіруйце свой тэлефон, паглядзеўшы на яго"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Каб выкарыстоўваць распазнаванне твару, уключыце "<b>"доступ да камеры"</b>" праз раздзел \"Налады &gt; Прыватнасць\""</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Наладзьце дадатковыя спосабы разблакіроўкі"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Націсніце, каб дадаць адбітак пальца"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Разблакіраванне адбіткам пальца"</string>
@@ -726,8 +729,8 @@
     <string name="permdesc_handoverStatus" msgid="3842269451732571070">"Дазваляе праграме атрымліваць інфармацыю аб бягучых перадачах Android Beam"</string>
     <string name="permlab_removeDrmCertificates" msgid="710576248717404416">"выдаленне сертыфікатаў DRM"</string>
     <string name="permdesc_removeDrmCertificates" msgid="4068445390318355716">"Дазваляе праграме выдаляць сертыфікаты DRM. Ніколі не павінна патрабавацца для звычайных праграм."</string>
-    <string name="permlab_bindCarrierMessagingService" msgid="3363450860593096967">"прывязка да службы паведамленняў аператара"</string>
-    <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"Дазваляе ўладальніку выконваць прывязку да інтэрфейсу верхняга ўзроўню службы паведамленняў аператара. Ніколі не павінна патрабавацца для звычайных праграм."</string>
+    <string name="permlab_bindCarrierMessagingService" msgid="3363450860593096967">"падключэнне да сэрвісу абмену паведамленнямі аператара"</string>
+    <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"Дазваляе ўладальніку выконваць падключэнне да базавага інтэрфейсу сэрвісу абмену паведамленнямі аператара. Звычайныя праграмы ніколі не выкарыстоўваюць гэты дазвол."</string>
     <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"прывязвацца з сэрвісаў аператара"</string>
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"Дазваляе ўладальніку ажыццяўляць прывязку да сэрвісаў аператара. Ніколі не павінна патрабавацца для звычайных праграм."</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"атрымліваць доступ да рэжыму «Не турбаваць»"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Падрыхтоўка <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Запуск прыкладанняў."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Завяршэнне загрузкі."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Выключыць экран?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Падчас наладкі адбітка пальца вы націскалі кнопку сілкавання.\n\nЗвычайна гэта дзеянне выключае экран."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Выключыць"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Скасаваць"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Працягнуць наладжванне?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Вы націснулі кнопку сілкавання. Звычайна ў выніку гэтага дзеяння выключаецца экран.\n\nПадчас наладжвання адбітка пальца злёгку дакраніцеся да кнопкі."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Выключыць экран"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Працягнуць наладку"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Працягнуць"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Прыкладанне \"<xliff:g id="APP">%1$s</xliff:g>\" запушчанае"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Націсніце, каб вярнуцца да гульні"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Выберыце гульню"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index d75d2e7..dad2f03 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Разрешава на приложението да използва услугата за незабавни съобщения за извършване на обаждания без намеса от ваша страна."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"четене на състоянието и идентификационните данни на телефона"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Разрешава на приложението достъп до телефонните функции на устройството. Това разрешение позволява на приложението да определя телефонния номер и идентификационния номер на устройството, дали се води разговор и отдалечения номер, до който е установена връзка с обаждането."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"четене на основното телефонно състояние и идентичност"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Разрешава на приложението да осъществява достъп до основните телефонни функции на устройството."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"маршрутизиране на обажданията чрез системата"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Разрешава на приложението да маршрутизира обажданията си чрез системата с цел подобряване на свързаната с тях практическа работа."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"вижда и управлява обажданията чрез системата."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Докоснете, за да изтриете модела на лицето си, след което добавете лицето си отново"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Настройване на отключването с лице"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Отключвайте телефона си, като го погледнете"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"За да използвате функцията „Отключване с лице“, включете "<b>"достъпа до камерата"</b>" от „Настройки &gt; Поверителност“"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Настройване на още начини за отключване на телефона"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Докоснете, за да добавите отпечатък"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Отключване с отпечатък"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> се подготвя."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Приложенията се стартират."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Зареждането завършва."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Да се изключи ли екранът?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"При настройването на отпечатъка си натиснахте бутона за захранване.\n\nТова действие обикновено изключва екрана."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Изключване"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Отказ"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Искате ли да продължите с настройването?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Натиснахте бутона за включване/изключване – това обикновено изключва екрана.\n\nОпитайте да докоснете леко, докато настройвате отпечатъка си."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Изключване на екрана"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Напред с настройв."</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Напред"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> се изпълнява"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Докоснете, за да се върнете към играта"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Избиране на игра"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 3080948..d560d5c 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"আপনার হস্তক্ষেপ ছাড়াই কল করতে অ্যাপ্লিকেশানটিকে IMS পরিষেবা ব্যবহারের অনুমতি দিন৷"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ফোনের স্থিতি এবং পরিচয় পড়ুন"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"অ্যাপ্লিকেশানটিকে ডিভাইসের ফোন বৈশিষ্ট্যগুলিকে অ্যাক্সেস করার অনুমতি দেয়৷ এই অনুমতিটি অ্যাপ্লিকেশানটিকে একটি কল সক্রিয় থাকা অবস্থায় এবং দূরবর্তী নম্বর একটি কল দ্বারা সংযুক্ত থাকাকালীনও ফোন নম্বর এবং ডিভাইসের IDগুলি নির্ধারণ করার অনুমতি দেয়৷"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"টেলিফোন সংক্রান্ত স্ট্যাটাস ও পরিচয় সংক্রান্ত প্রাথমিক বিষয় পড়ুন"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"অ্যাপটিকে ডিভাইসের টেলিফোন সংক্রান্ত প্রাথমিক ফিচারগুলি অ্যাক্সেস করার অনুমতি দিন।"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"সিস্টেমের মাধ্যমে কলগুলি রুট করতে দিন"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"কল করার অভিজ্ঞতা উন্নত করার জন্য অ্যাপকে সিস্টেমের মাধ্যমে তার কলগুলি রুট করতে দেয়।"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"সিস্টেমের মাধ্যমে কল দেখা এবং নিয়ন্ত্রণ করা।"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"আপনার ফেস মডেল মুছে দেওয়ার জন্য ট্যাপ করুন এবং তারপরে আবার ফেস যোগ করুন"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"\'ফেস আনলক\' সেট আপ করুন"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"আপনার ফোনের দিকে তাকিয়ে এটিকে আনলক করুন"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"\'ফেস আনলক\' ফিচার ব্যবহার করতে \'সেটিংস ও গোপনীয়তা\' বিকল্পে গিয়ে "<b>"ক্যামেরায় অ্যাক্সেস দিন"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"আনলক করার জন্য বিভিন্ন উপায়ে সেট আপ করুন"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"একটি আঙ্গুলের ছাপ যোগ করতে ট্যাপ করুন"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ফিঙ্গারপ্রিন্ট আনলক"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> প্রস্তুত করা হচ্ছে৷"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"অ্যাপ্লিকেশানগুলি শুরু করা হচ্ছে৷"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"চালু করা সম্পূর্ণ হচ্ছে৷"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"স্ক্রিন বন্ধ করবেন?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"আপনার আঙ্গুলের ছাপ সেট আপ করার সময়, পাওয়ার বোতাম প্রেস করেছিলেন।\n\nএর ফলে সাধারণত আপনার স্ক্রিন বন্ধ হয়ে যায়।"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"বন্ধ করুন"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"বাতিল করুন"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"সেট-আপ করা চালিয়ে যাবেন?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"আপনি \'পাওয়ার\' বোতাম প্রেস করেছেন — এর ফলে সাধারণত স্ক্রিন বন্ধ হয়ে যায়।\n\nআঙ্গুলের ছাপ সেট-আপ করার সময় হালকাভাবে ট্যাপ করে দেখুন।"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"স্ক্রিন বন্ধ করুন"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"সেট-আপ চালিয়ে যান"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"এগিয়ে যান"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> চলছে"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"গেমে ফিরে আসতে ট্যাপ করুন"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"গেম বেছে নিন"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index ea37490..68ccf15 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -474,6 +474,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Omogućava aplikaciji da koristi IMS uslugu za pozivanje bez vaše intervencije."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"čitanje statusa i identiteta telefona"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Omogućava aplikaciji pristup telefonskim funkcijama uređaja. Ovo odobrenje omogućava aplikaciji određivanje telefonskog i identifikacionog broja uređaja, bez obzira da li je poziv aktivan i da li je uspostavljena veza sa pozivanim brojem."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"očitavanje osnovnog telefonskog statusa i identiteta"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Dozvoljava aplikaciji pristup osnovnim telefonskim funkcijama uređaja."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"usmjeravanje poziva preko sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Dopušta aplikaciji da pozive usmjeri preko sistema radi poboljšanja iskustva pozivanja."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"vidjeti i kontrolirati pozive preko sistema."</string>
@@ -625,6 +627,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Dodirnite da izbrišete model lica, a zatim ponovo dodajte lice"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Postavite otključavanje licem"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Otključajte telefon gledajući u njega"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Da koristite otključavanje licem, uključite "<b>"Pristup kameri"</b>" u meniju Postavke &gt; Privatnost"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Postavite više načina otključavanja"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Dodirnite da dodate otisak prsta"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Otključavanje otiskom prsta"</string>
@@ -1296,10 +1299,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Pripremanje aplikacije <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Pokretanje aplikacija."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Pokretanje pri kraju."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Isključiti ekran?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Prilikom postavljanja otiska prsta, pritisnuli ste dugme za uključivanje.\n\nTime se obično isključuje ekran."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Isključi"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Otkaži"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Nastaviti postavljanje?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pritisnuli ste dugme za uključivanje. Tako se obično isključuje ekran.\n\nPokušajte ga lagano dodirnuti dok postavljate otisak prsta."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Isključi ekran"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Nastavi postavljanje"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Nastaviti s potvrđivanjem otiska prsta?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pritisnuli ste dugme za uključivanje. Tako se obično isključuje ekran.\n\nPokušajte ga lagano dodirnuti da potvrdite otisak prsta."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Isključi ekran"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Nastavi"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Pokrenuta je aplikacija <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Dodirnite za povratak u igru"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Odaberite igru"</string>
@@ -2049,7 +2056,7 @@
     <string name="demo_restarting_message" msgid="1160053183701746766">"Vraćanje uređaja na početne postavke…"</string>
     <string name="suspended_widget_accessibility" msgid="6331451091851326101">"Onemogućen <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="5731633152336490471">"Konferencijski poziv"</string>
-    <string name="tooltip_popup_title" msgid="7863719020269945722">"Savjet za alat"</string>
+    <string name="tooltip_popup_title" msgid="7863719020269945722">"Skočni opis"</string>
     <string name="app_category_game" msgid="4534216074910244790">"Igre"</string>
     <string name="app_category_audio" msgid="8296029904794676222">"Muzika i zvuk"</string>
     <string name="app_category_video" msgid="2590183854839565814">"Filmovi i videozapisi"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 91ba2f0..726bb3f 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permet que l\'aplicació utilitzi el servei IMS per fer trucades sense la teva intervenció."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"veure l\'estat i la identitat del telèfon"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permet que l\'aplicació accedeixi a les funcions de telèfon del dispositiu. Aquest permís permet que l\'aplicació determini el número de telèfon i els identificadors del dispositiu, si hi ha una trucada activa i el número remot connectat amb una trucada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"llegir la identitat i l\'estat bàsics de telefonia"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permet que l\'aplicació accedeixi a les funcions de telefonia bàsiques del dispositiu."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"encaminar trucades a través del sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permet que l\'aplicació encamini les trucades a través del sistema per millorar-ne l\'experiència."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"consulta i controla les trucades a través del sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toca per suprimir el teu model facial i, a continuació, torna a afegir la teva cara"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configura Desbloqueig facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Mira el telèfon per desbloquejar-lo"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Per utilitzar Desbloqueig facial, activa "<b>"Accés a la càmera"</b>" a Configuració &gt; Privadesa"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configura més maneres de desbloquejar el dispositiu"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Toca per afegir una empremta digital"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueig amb empremta digital"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"S\'està preparant <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"S\'estan iniciant les aplicacions."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"S\'està finalitzant l\'actualització."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vols apagar la pantalla?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Mentre configuraves la teva empremta digital has premut el botó d\'engegada.\n\nAixò normalment apaga la pantalla."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Desactiva"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancel·la"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Vols continuar amb la configuració?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Has premut el botó d\'engegada, fet que sol apagar la pantalla.\n\nProva de tocar-lo lleugerament en configurar l\'empremta digital."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Apaga la pantalla"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continua configurant"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Vols continuar verificant l\'empremta?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Has premut el botó d\'engegada, fet que sol apagar la pantalla.\n\nProva de tocar-lo lleugerament per verificar l\'empremta digital."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Apaga la pantalla"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continua"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> s\'està executant"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Toca per tornar al joc"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Tria el joc"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index c928655..abbcc2c 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Umožňuje aplikaci používat službu zasílání rychlých zpráv k uskutečňování hovorů bez vašeho zásahu."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"čtení stavu a identity telefonu"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Umožňuje aplikaci získat přístup k telefonním funkcím zařízení. Toto oprávnění umožňuje aplikaci zjistit telefonní číslo telefonu, identifikační čísla zařízení, zda zrovna probíhá hovor, a vzdálené číslo, ke kterému je hovor připojen."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"čtení základního stavu a identity související s telefonováním"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Umožňuje aplikaci přístup k základním telefonickým funkcím zařízení."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"směrování volání prostřednictvím systému"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Umožňuje aplikaci směrovat volání prostřednictvím systému za účelem vylepšení funkcí volání."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"zobrazení a ovládání hovorů v systému."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Klepnutím svůj model obličeje smažte a potom ho přidejte znovu"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Nastavte odemknutí obličejem"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Telefon odemknete pohledem"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Pokud chcete používat odemknutí obličejem, v Nastavení &gt; Soukromí zapnetě "<b>"přístup k fotoaparátu"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Nastavte si více způsobů odemykání"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Klepnutím přidáte otisk prstu"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Odemknutí otiskem prstu"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Příprava aplikace <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Spouštění aplikací."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Dokončování inicializace."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vypnout obrazovku?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Při nastavování otisku prstu jste stiskli vypínač.\n\nTen obvykle vypne obrazovku."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Vypnout"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Zrušit"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Pokračovat v nastavování?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Stiskli jste vypínač – tím se obvykle vypíná obrazovka.\n\nPři nastavování otisku prstu je třeba klepat lehce."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Vypnout obrazovku"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Pokračovat v nastavení"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Pokračovat v ověřování otisku prstu?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Stiskli jste vypínač – tím se obvykle vypíná obrazovka.\n\nZkuste lehkým klepnutím ověřit svůj otisk prstu."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Vypnout obrazovku"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Pokračovat"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Klepnutím se vrátíte do hry"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Vyberte hru"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 4c60718..57673f4 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Tillader, at appen kan bruge chat-tjenesten til at foretage opkald, uden du gør noget."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"læse telefonens status og identitet"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Tillader, at appen kan få adgang til telefonfunktionerne på enheden. Med denne tilladelse kan appen fastslå telefonnummeret og enheds-id\'erne, hvorvidt et opkald er aktivt samt det eksterne nummer, der oprettes forbindelse til via et opkald."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"tilgå grundlæggende oplysninger om telefonistatus og -identitet"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Giver appen adgang til enhedens grundlæggende telefonifunktioner."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"dirigere opkald gennem systemet"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Tillader appen at dirigere sine opkald gennem systemet for at forbedre opkaldsoplevelsen."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"se og styre opkald via systemet."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tryk for at slette din ansigtsmodel, og tilføj derefter dit ansigt igen"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Konfigurer ansigtslås"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Lås din telefon op ved at kigge på den"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Hvis du vil bruge ansigtslåsen, skal du aktivere "<b>"Kameraadgang"</b>" under Indstillinger &gt; Privatliv"</string>
     <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>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Forbereder <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Åbner dine apps."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Gennemfører start."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vil du slukke skærmen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Ved konfigurationen af dit fingeraftryk trykkede du på afbryderknappen.\n\nDet medfører normalt, at din skærm slukkes."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Sluk"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Annuller"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Vil du fortsætte konfigurationen?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Du har trykket på afbryderknappen, hvilket som regel slukker skærmen.\n\nPrøv at trykke let på knappen, mens du konfigurerer dit fingeraftryk."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Sluk skærm"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Fortsæt"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Vil du bekræfte dit fingeraftryk?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Du har trykket på afbryderknappen, hvilket som regel slukker skærmen.\n\nPrøv at trykke let på knappen for at bekræfte dit fingeraftryk."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Sluk skærm"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Fortsæt"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> er i gang"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tryk for at vende tilbage til spillet"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Vælg et spil"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 25ba98b..fde9b0c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Ermöglicht der App die Verwendung des IMS-Dienstes zum Tätigen von Anrufen ohne Nutzereingriffe"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"Telefonstatus und Identität abrufen"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Ermöglicht der App, auf die Telefonfunktionen des Geräts zuzugreifen. Die Berechtigung erlaubt der App, die Telefonnummer und Geräte-IDs zu erfassen, festzustellen, ob gerade ein Gespräch geführt wird, und die Rufnummer verbundener Anrufer zu lesen."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"Grundlegenden Telefoniestatus und Identität lesen"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Ermöglicht der App, auf die grundlegenden Telefoniefunktionen des Geräts zuzugreifen."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"Anrufe über das System durchführen"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Ermöglicht der App, Anrufe über das System durchzuführen, um die Anrufqualität zu verbessern."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Anrufe durch das System einsehen und verwalten."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tippe, um dein Gesichtsmodell zu löschen, und füge es dann noch einmal hinzu"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Entsperrung per Gesichtserkennung einrichten"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Entsperre dein Smartphone, indem du es ansiehst"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Wenn du die Entsperrung per Gesichtserkennung verwenden möchtest, aktiviere in den Einstellungen unter „Datenschutz“ die Option "<b>"Kamerazugriff"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Weitere Möglichkeiten zum Entsperren einrichten"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tippe, um einen Fingerabdruck hinzuzufügen"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Entsperrung per Fingerabdruck"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> wird vorbereitet"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Apps werden gestartet..."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Start wird abgeschlossen..."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Display ausschalten?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Beim Einrichten deines Fingerabdrucks hast du die Ein-/Aus-Taste gedrückt.\n\nDamit wird üblicherweise das Display ausgeschaltet."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Ausschalten"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Abbrechen"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Einrichtung fortsetzen?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Du hast die Ein-/Aus-Taste gedrückt — damit wird der Bildschirm ausgeschaltet.\n\nTippe die Taste leicht an, um deinen Fingerabdruck einzurichten."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Ausschalten"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Weiter einrichten"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Mit der Fingerabdruckprüfung fortfahren?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Du hast die Ein-/Aus-Taste gedrückt — damit wird der Bildschirm ausgeschaltet.\n\nTippe die Taste leicht an, um mit deinem Fingerabdruck deine Identität zu bestätigen."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Ausschalten"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Fortfahren"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tippe, um zum Spiel zurückzukehren"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Spiel wählen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 4764e6a..b0ad4da 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Επιτρέπει στην εφαρμογή τη χρήση της υπηρεσίας IMS για την πραγματοποίηση κλήσεων χωρίς τη δική σας παρέμβαση."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"διαβάζει την κατάσταση και ταυτότητα τηλεφώνου"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Επιτρέπει στην εφαρμογή την πρόσβαση στις λειτουργίες τηλεφώνου της συσκευής. Αυτή η άδεια δίνει τη δυνατότητα στην εφαρμογή να καθορίζει τον αριθμό τηλεφώνου και τα αναγνωριστικά συσκευών, εάν μια κλήση είναι ενεργή, καθώς και τον απομακρυσμένο αριθμό που συνδέεται από μια κλήση."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ανάγνωση βασικών πληροφοριών κατάστασης και ταυτότητας τηλεφωνίας"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Επιτρέπει στην εφαρμογή να έχει πρόσβαση στις βασικές λειτουργίες τηλεφωνίας της συσκευής."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"δρομολόγηση κλήσεων μέσω του συστήματος"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Επιτρέπει στην εφαρμογή να δρομολογεί τις κλήσεις της μέσω του συστήματος για να βελτιώσει την εμπειρία κλήσης."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"προβολή και έλεγχος κλήσεων μέσω του συστήματος."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Πατήστε για να διαγράψετε το μοντέλο προσώπου και, στη συνέχεια, προσθέστε το πρόσωπό σας ξανά."</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Ρύθμιση της λειτουργίας Ξεκλείδωμα με το πρόσωπο"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Ξεκλειδώστε το τηλέφωνό σας απλώς κοιτώντας το"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Για να χρησιμοποιήσετε τη λειτουργία Ξεκλείδωμα με το πρόσωπο, ενεργοποιήστε την επιλογή "<b>"Πρόσβαση στην κάμερα"</b>" από το μενού Ρυθμίσεις &gt; Απόρρητο"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Ρυθμίστε περισσότερους τρόπους ξεκλειδώματος"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Πατήστε για να προσθέσετε δακτυλικό αποτύπωμα"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Ξεκλείδωμα με δακτυλικό αποτύπωμα"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Προετοιμασία <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Έναρξη εφαρμογών."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Ολοκλήρωση εκκίνησης."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Απενεργοποίηση οθόνης;"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Κατά τη ρύθμιση του δακτυλικού σας αποτυπώματος, πατήσατε το κουμπί λειτουργίας.\n\nΑυτό απενεργοποιεί συνήθως την οθόνη."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Απενεργοποίηση"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Ακύρωση"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Συνέχιση ρύθμισης;"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Πατήσατε το κουμπί λειτουργίας. Αυτό συνήθως απενεργοποιεί την οθόνη.\n\nΔοκιμάστε να πατήσετε απαλά κατά τη ρύθμιση του δακτυλικού σας αποτυπώματος."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Απενεργοπ. οθόνης"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Συνέχιση ρύθμισης"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Συνέχεια"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Πατήστε για να επιστρέψετε στο παιχνίδι"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Επιλέξτε παιχνίδι"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index a7647b6..fd68ddd 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -471,6 +471,8 @@
     <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="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>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tap to delete your face model, then add your face again"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Set up Face Unlock"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Unlock your phone by looking at it"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"To use Face Unlock, turn on "<b>"Camera access"</b>" in Settings &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Set up more ways to unlock"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tap to add a fingerprint"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingerprint Unlock"</string>
@@ -1276,10 +1279,14 @@
     <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_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Turn off screen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"While setting up your fingerprint, you pressed the power button.\n\nThis usually turns off your screen."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Turn off"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancel"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continue setup?"</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_positive_button" msgid="2095415838459356833">"Turn off screen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continue setup"</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_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>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tap to return to game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choose game"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 8c28433..265d2e6 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -471,6 +471,8 @@
     <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="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>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tap to delete your face model, then add your face again"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Set up Face Unlock"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Unlock your phone by looking at it"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"To use Face Unlock, turn on "<b>"Camera access"</b>" in Settings &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Set up more ways to unlock"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tap to add a fingerprint"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingerprint Unlock"</string>
@@ -1276,10 +1279,14 @@
     <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_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Turn off screen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"While setting up your fingerprint, you pressed the power button.\n\nThis usually turns off your screen."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Turn off"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancel"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continue setup?"</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_positive_button" msgid="2095415838459356833">"Turn off screen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continue setup"</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_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>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tap to return to game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choose game"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 6e25954..8ee21a3 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -471,6 +471,8 @@
     <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="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>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tap to delete your face model, then add your face again"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Set up Face Unlock"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Unlock your phone by looking at it"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"To use Face Unlock, turn on "<b>"Camera access"</b>" in Settings &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Set up more ways to unlock"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tap to add a fingerprint"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingerprint Unlock"</string>
@@ -1276,10 +1279,14 @@
     <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_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Turn off screen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"While setting up your fingerprint, you pressed the power button.\n\nThis usually turns off your screen."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Turn off"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancel"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continue setup?"</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_positive_button" msgid="2095415838459356833">"Turn off screen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continue setup"</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_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>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tap to return to game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choose game"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 53031c69..7fada24 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -471,6 +471,8 @@
     <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="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>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tap to delete your face model, then add your face again"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Set up Face Unlock"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Unlock your phone by looking at it"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"To use Face Unlock, turn on "<b>"Camera access"</b>" in Settings &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Set up more ways to unlock"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tap to add a fingerprint"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingerprint Unlock"</string>
@@ -1276,10 +1279,14 @@
     <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_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Turn off screen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"While setting up your fingerprint, you pressed the power button.\n\nThis usually turns off your screen."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Turn off"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancel"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continue setup?"</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_positive_button" msgid="2095415838459356833">"Turn off screen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continue setup"</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_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>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tap to return to game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choose game"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 0f0e35a..0eaf8ef 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -471,6 +471,8 @@
     <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="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>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‎Tap to delete your face model, then add your face again‎‏‎‎‏‎"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎Set up Face Unlock‎‏‎‎‏‎"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‎Unlock your phone by looking at it‎‏‎‎‏‎"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎To use Face Unlock, turn on ‎‏‎‎‏‏‎"<b>"‎‏‎‎‏‏‏‎Camera access‎‏‎‎‏‏‎"</b>"‎‏‎‎‏‏‏‎ in Settings &gt; Privacy‎‏‎‎‏‎"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‎‏‏‏‎Set up more ways to unlock‎‏‎‎‏‎"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎Tap to add a fingerprint‎‏‎‎‏‎"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‎‎Fingerprint Unlock‎‏‎‎‏‎"</string>
@@ -1276,10 +1279,14 @@
     <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_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎Turn off screen?‎‏‎‎‏‎"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎While setting up your fingerprint, you pressed the Power button.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎This usually turns off your screen.‎‏‎‎‏‎"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎Turn off‎‏‎‎‏‎"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎Cancel‎‏‎‎‏‎"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎Continue setup?‎‏‎‎‏‎"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‏‎‎‎‎‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎You pressed the power button — this usually turns off the screen.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Try tapping lightly while setting up your fingerprint.‎‏‎‎‏‎"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‎Turn off screen‎‏‎‎‏‎"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‏‏‎‏‏‎Continue setup‎‏‎‎‏‎"</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‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎Try 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>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‎Tap to return to game‎‏‎‎‏‎"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‎‏‏‎‎Choose game‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index e7110c9..93a316f 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -329,17 +329,17 @@
     <string name="permgrouplab_notifications" msgid="5472972361980668884">"Notificaciones"</string>
     <string name="permgroupdesc_notifications" msgid="4608679556801506580">"mostrar notificaciones"</string>
     <string name="capability_title_canRetrieveWindowContent" msgid="7554282892101587296">"Recuperar el contenido de las ventanas"</string>
-    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspecciona el contenido de la ventana con la que estés interactuando."</string>
+    <string name="capability_desc_canRetrieveWindowContent" msgid="6195610527625237661">"Inspeccionará el contenido de la ventana con la que estés interactuando."</string>
     <string name="capability_title_canRequestTouchExploration" msgid="327598364696316213">"Activar la Exploración táctil"</string>
     <string name="capability_desc_canRequestTouchExploration" msgid="4394677060796752976">"Los elementos que presiones se dirán en voz alta y podrás explorar la pantalla mediante gestos."</string>
     <string name="capability_title_canRequestFilterKeyEvents" msgid="2772371671541753254">"Observar el texto que escribes"</string>
     <string name="capability_desc_canRequestFilterKeyEvents" msgid="2381315802405773092">"Incluye datos personales, como números de tarjeta de crédito y contraseñas."</string>
     <string name="capability_title_canControlMagnification" msgid="7701572187333415795">"Controlar la ampliación de pantalla"</string>
-    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controla el posicionamiento y el nivel de zoom de la pantalla."</string>
+    <string name="capability_desc_canControlMagnification" msgid="2206586716709254805">"Controlará el posicionamiento y el nivel de zoom de la pantalla."</string>
     <string name="capability_title_canPerformGestures" msgid="9106545062106728987">"Usar gestos"</string>
     <string name="capability_desc_canPerformGestures" msgid="6619457251067929726">"Permite presionar, deslizar, pellizcar y usar otros gestos."</string>
     <string name="capability_title_canCaptureFingerprintGestures" msgid="1189053104594608091">"Gestos del sensor de huellas dactilares"</string>
-    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Captura los gestos que se hacen en el sensor de huellas dactilares del dispositivo."</string>
+    <string name="capability_desc_canCaptureFingerprintGestures" msgid="6861869337457461274">"Capturará los gestos que se hacen en el sensor de huellas dactilares del dispositivo."</string>
     <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tomar captura de pantalla"</string>
     <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puede tomar una captura de la pantalla."</string>
     <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra de estado"</string>
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite que la aplicación utilice el servicio IMS para hacer llamadas sin tu intervención."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"leer la identidad y el estado del dispositivo"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite que la aplicación acceda a las funciones de teléfono del dispositivo. La aplicación puede utilizar este permiso para descubrir identificadores de dispositivos y números de teléfono, para saber si una llamada está activa y para conocer el número remoto con el que se ha establecido conexión mediante una llamada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"leer identidad y estado de telefonía básica"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite que la app acceda a las funciones de telefonía básica del dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"Transmite llamadas a través del sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite que la app transmita las llamadas a través del sistema para mejorar la experiencia de llamadas."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Mirar y controlar las llamadas con el sistema"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Presiona para borrar el modelo de rostro y, luego, vuelve a agregar tu rostro"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configura Desbloqueo facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Desbloquea el teléfono con solo mirarlo"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para usar Desbloqueo facial, activa el "<b>"Acceso a la cámara"</b>" en Configuración y privacidad"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configura más formas de desbloquear el dispositivo"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Presiona para agregar una huella dactilar"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueo con huellas dactilares"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Preparando <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Iniciando aplicaciones"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Finalizando el inicio"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"¿Quieres apagar la pantalla?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Al configurar tu huella dactilar, presionaste el botón de encendido.\n\nPor lo general, esta acción apaga la pantalla."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Apagar"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancelar"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"¿Continuar con la configuración?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Presionaste el botón de encendido. Por lo general, esta acción apaga la pantalla.\n\nPresiona suavemente mientras configuras tu huella dactilar."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Apagar pantalla"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continuar config."</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"¿Verificar huella dactilar?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Presionaste el botón de encendido. Por lo general, esta acción apaga la pantalla.\n\nPresiona suavemente para verificar tu huella dactilar."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Apagar pantalla"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuar"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Presiona para volver al juego"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Elige un juego"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index a582a09..22de5d1 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite que la aplicación utilice el servicio IMS para realizar llamadas sin tu intervención."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"consultar la identidad y el estado del teléfono"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite que la aplicación acceda a las funciones de teléfono del dispositivo. La aplicación puede utilizar este permiso para descubrir identificadores de dispositivos y números de teléfono, para saber si una llamada está activa y para conocer el número remoto con el que se ha establecido conexión mediante una llamada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"leer el estado y la identidad básicos de telefonía"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite que la aplicación acceda a las funciones de telefonía básicas del dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"direccionar llamadas a través del sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite a la aplicación direccionar sus llamadas hacia el sistema para mejorar la calidad de estas."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ver y controlar llamadas a través del sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toca para eliminar tu modelo facial y luego añade de nuevo tu cara"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configura Desbloqueo facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Desbloquea el teléfono con solo mirarlo"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para usar Desbloqueo Facial, habilita el "<b>"acceso a la cámara"</b>" en Ajustes y privacidad"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configura más formas de desbloqueo"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Toca para añadir una huella digital"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueo con huella digital"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Preparando <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Iniciando aplicaciones"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Finalizando inicio..."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"¿Apagar pantalla?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Has pulsado el botón de encendido mientras configurabas tu huella digital.\n\nAl hacer esto, se suele apagar la pantalla."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Desactivar"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancelar"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"¿Quieres seguir con la configuración?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Has pulsado el botón de encendido, lo que suele apagar la pantalla.\n\nPrueba a apoyar el dedo ligeramente para verificar tu huella digital."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Apagar pantalla"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Seguir configurando"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"¿Seguir verificando tu huella digital?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Has pulsado el botón de encendido, lo que suele apagar la pantalla.\n\nPrueba a apoyar el dedo ligeramente para verificar tu huella digital."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Apagar pantalla"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuar"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Toca para volver al juego"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Elegir juego"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 84912d8..334a666 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Võimaldab rakendusel kasutada IMS-teenust kõnede tegemiseks ilma, et peaksite sekkuma."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"Telefoni oleku ja identiteedi lugemine"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Annab rakendusele juurdepääsu seadme telefonifunktsioonidele. See luba võimaldab rakendusel määrata telefoninumbri ja seadme ID-d ning kontrollida, kas kõne on aktiivne ja kaugnumber on kõne kaudu telefoniga ühendatud."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"Lugeda peamiste telefonifunktsioonide olekut ja kasutajate identiteeti"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Võimaldab rakendusel pääseda juurde seadme peamistele telefonifunktsioonidele."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"kõnede marsruutimine süsteemi kaudu"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Võimaldab rakendusel kõnesid süsteemi kaudu marsruutida, et helistamiskogemust täiustada."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"süsteemi kaudu kõnede vaatamine ja juhtimine."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Puudutage näomudeli kustutamiseks, seejärel lisage oma nägu uuesti"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Näoga avamise seadistamine"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Avage telefon seda vaadates"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Näoga avamise kasutamiseks lülitage menüüs Seaded &gt; Privaatsus sisse "<b>"juurdepääs kaamerale"</b>"."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Seadistage rohkem viise avamiseks"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Puudutage sõrmejälje lisamiseks"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Sõrmejäljega avamine"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Rakenduse <xliff:g id="APPNAME">%1$s</xliff:g> ettevalmistamine."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Rakenduste käivitamine."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Käivitamise lõpuleviimine."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Kas lülitada ekraan välja?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Sõrmejälje seadistamisel vajutasite toitenuppu.\n\nSee lülitab ekraanikuva tavaliselt välja."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Lülita välja"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Tühista"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Kas jätkata seadistamist?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Vajutasite toitenuppu – tavaliselt lülitab see ekraani välja.\n\nPuudutage õrnalt ja seadistage oma sõrmejälg."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Lülita ekraan välja"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Jätka seadistamist"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Kas jätkata sõrmejälje kinnitamist?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Vajutasite toitenuppu – tavaliselt lülitab see ekraani välja.\n\nPuudutage õrnalt, et oma sõrmejälg kinnitada."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Lülita ekraan välja"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Jätka"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> töötab"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Puudutage mängu naasmiseks"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Valige mäng"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index d84bdddd..df16b73 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Zuk ezer egin beharrik gabe deiak egiteko IMS zerbitzua erabiltzeko baimena ematen die aplikazioei."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"irakurri telefonoaren egoera eta identitatea"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Gailuaren telefono-eginbideak atzitzeko baimena ematen die aplikazioei. Baimen horrek aplikazioari telefono-zenbakia eta gailu IDak zein diren, deirik aktibo dagoen eta deia zer zenbakirekin konektatuta dagoen zehazteko baimena ematen die aplikazioei."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"irakurri oinarrizko egoera telefonikoa eta identitatea"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Gailuaren oinarrizko eginbide telefonikoak atzitzeko baimena ematen dio aplikazioari."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"bideratu deiak sistemaren bidez"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Deiak sistemaren bidez bideratzea baimentzen die aplikazioei, deien zerbitzua ahal bezain ona izan dadin."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ikusi eta kontrolatu deiak sistemaren bidez."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Sakatu hau aurpegi-eredua ezabatzeko eta, gero, gehitu aurpegia berriro"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Konfiguratu aurpegi bidez desblokeatzeko eginbidea"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Telefonoa desblokeatzeko, begira iezaiozu"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aurpegi bidez desblokeatzeko aukera erabiltzeko, aktibatu "<b>"kamera atzitzeko baimena"</b>" Ezarpenak &gt; Pribatutasuna atalean"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfiguratu telefonoa desblokeatzeko modu gehiago"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Sakatu hau hatz-marka bat gehitzeko"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Hatz-marka bidez desblokeatzea"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> prestatzen."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Aplikazioak abiarazten."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Bertsio-berritzea amaitzen."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Pantaila itzali nahi duzu?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Hatz-marka konfiguratzean, etengailua sakatu duzu.\n\nNormalean, horren ondorioz pantaila itzali egiten da."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Desaktibatu"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Utzi"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Konfiguratzen jarraitu nahi duzu?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Etengailua sakatu duzu; pantaila itzaltzeko balio du horrek.\n\nUki ezazu arin, hatz-marka konfiguratu bitartean."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Itzali pantaila"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Jarraitu konfiguratzen"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Hatz-marka egiaztatzen jarraitu nahi duzu?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Etengailua sakatu duzu; pantaila itzaltzeko balio du horrek.\n\nUki ezazu arin, hatz-marka egiaztatzeko."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Itzali pantaila"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Egin aurrera"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> abian da"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Sakatu jokora itzultzeko"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Aukeratu joko bat"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 8f1daa21..e66c783 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"‏به برنامه اجازه می‌دهد از سرویس IMS برای برقراری تماس‌ها بدون دخالت شما استفاده کند."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"خواندن وضعیت تلفن و شناسه"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"به برنامه اجازه می‌دهد به ویژگی‌های تلفن دستگاه شما دسترسی پیدا کند. این مجوز به برنامه اجازه می‌دهد شماره تلفن و شناسه‌های دستگاه، فعال بودن یک تماس و شماره راه دوری که با یک تماس متصل شده است را مشخص کند."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"خواندن شناسه و وضعیت اصلی ارتباط دوربرد"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"به برنامه اجازه می‌دهد به ویژگی‌های اصلی ارتباط دوربرد دستگاه دسترسی داشته باشد."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"برقرار کردن تماس‌ها ازطریق سیستم"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"به برنامه امکان می‌دهد برای بهبود تجربه تماس، تماس‌هایش را ازطریق سیستم برقرار کند."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"دیدن و کنترل تماس‌ها ازطریق سیستم."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"برای حذف مدل چهره‌تان ضربه بزنید، سپس چهره‌تان را دوباره اضافه کنید"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"راه‌اندازی «قفل‌گشایی با چهره»"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"برای باز کردن قفل تلفن خود به آن نگاه کنید"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"‏برای استفاده از «قفل‌گشایی با چهره»، "<b>"دسترسی به دوربین"</b>" را در «تنظیمات &gt; حریم‌خصوصی» روشن کنید"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"راه‌اندازی روش‌های بیشتر برای باز کردن قفل"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"برای افزودن اثر انگشت، ضربه بزنید"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"قفل‌گشایی با اثر انگشت"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"آماده‌سازی <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"درحال آغاز کردن برنامه‌ها."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"درحال اتمام راه‌اندازی."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"صفحه‌نمایش خاموش شود؟"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"هنگام راه‌اندازی اثر انگشتتان، دکمه روشن/ خاموش را فشار دادید.\n\nاین کار معمولاً صفحه‌نمایش را خاموش می‌کند."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"خاموش شود"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"لغو شود"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"راه‌اندازی ادامه یابد؟"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"دکمه روشن/ خاموش را فشار دادید — این کار معمولاً صفحه‌نمایش را خاموش می‌کند.\n\nهنگام راه‌اندازی اثر انگشت، آرام ضربه بزنید."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"خاموش کردن صفحه"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ادامه راه‌اندازی"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ادامه"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> در حال اجرا"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"برای برگشت به بازی، ضربه بزنید"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"انتخاب بازی"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index b63e729..ff17fc1 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Antaa sovelluksen soittaa puheluita pikaviestipalvelun avulla ilman käyttäjän toimia."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lue puhelimen tila ja identiteetti"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Antaa sovelluksen käyttää laitteen puhelinominaisuuksia. Sovellus voi määrittää puhelinnumeron ja laitteen tunnuksen, puhelun tilan sekä soitetun numeron."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lukea tavallisten puheluominaisuuksien tila- ja identiteettitiedot"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Sallii sovellukselle pääsyn laitteen tavallisiin puheluominaisuuksiin."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ohjata puhelut järjestelmän kautta"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Tämä sallii sovelluksen ohjata puhelut järjestelmän kautta, mikä auttaa parantamaan puhelujen laatua."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"nähdä puhelut ja päättää niistä järjestelmässä"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Poista kasvomalli napauttamalla ja lisää sitten kasvosi uudelleen"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Ota kasvojentunnistusavaus käyttöön"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Avaa puhelimesi lukitus katsomalla laitetta"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Jos haluat käyttää kasvojentunnistusavausta, valitse Asetukset &gt; Yksityisyys ja laita "<b>"Pääsy kameraan"</b>" päälle"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Ota käyttöön lisää tapoja avata lukitus"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Napauta lisätäksesi sormenjälki"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Sormenjälkiavaus"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Valmistellaan: <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Käynnistetään sovelluksia."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Viimeistellään päivitystä."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Sammutetaanko näyttö?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Painoit virtapainiketta ottaessasi sormenjäljen käyttöön.\n\nTämä saa yleensä näytön sammumaan."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Laita pois päältä"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Peru"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Haluatko jatkaa?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Painoit virtapainiketta, mikä yleensä sammuttaa näytön.\n\nKosketa painiketta kevyesti tallentaessasi sormenjälkeä."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Sammuta näyttö"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Jatka käyttöönottoa"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Jatketaanko sormenjäljen vahvistamista?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Painoit virtapainiketta, mikä yleensä sammuttaa näytön.\n\nVahvista sormenjälki koskettamalla painiketta kevyesti."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Sammuta näyttö"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Jatka"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> käynnissä"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Palaa peliin napauttamalla"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Valitse peli"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index cae01af..e367f32 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permet à l\'application d\'utiliser le service IMS pour faire des appels sans votre intervention."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"voir l\'état et l\'identité du téléphone"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permet à l\'application d\'accéder aux fonctionnalités téléphoniques de l\'appareil. Cette autorisation permet à l\'application de déterminer le numéro de téléphone et les identifiants de l\'appareil, si un appel est actif et le numéro distant connecté par un appel."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"Lire l\'état et l\'identité de la téléphonie de base"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permet à l\'application d\'accéder aux fonctionnalités de téléphonie de base de l\'appareil."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"acheminer les appels dans le système"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permet à l\'application d\'acheminer ses appels dans le système afin d\'améliorer l\'expérience d\'appel."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"afficher et gérer les appels à l\'aide du système."</string>
@@ -622,6 +624,8 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Touchez pour supprimer votre modèle facial, puis ajoutez votre visage de nouveau"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurer le déverrouillage par reconnaissance faciale"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Déverrouillez votre téléphone en le regardant"</string>
+    <!-- no translation found for face_sensor_privacy_enabled (7407126963510598508) -->
+    <skip />
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configurer d\'autres méthodes de déverrouillage"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Touchez pour ajouter une empreinte digitale"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Déverrouillage par empreinte digitale"</string>
@@ -1276,10 +1280,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Préparation de <xliff:g id="APPNAME">%1$s</xliff:g> en cours…"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Lancement des applications…"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Finalisation de la mise à jour."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Éteindre l\'écran?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Pendant que vous configuriez votre empreinte digitale, vous avez appuyé sur l\'interrupteur.\n\nAppuyer sur ce bouton ferme habituellement l\'écran."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Éteindre"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Annuler"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Poursuivre la configuration?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Vous avez appuyé sur le l\'interrupteur – cette action éteint habituellement l\'écran.\n\nEssayez de toucher légèrement pendant la configuration de votre empreinte digitale."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Éteindre l\'écran"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Poursuivre configu."</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Poursuivre vérifica. empreinte digitale?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Vous avez appuyé sur le l\'interrupteur – cette action éteint habituellement l\'écran.\n\nEssayez de toucher légèrement pour vérifier votre empreinte digitale."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Éteindre l\'écran"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuer"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Touchez pour revenir au jeu"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choisissez un jeu"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3955c9f..e00304b 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permet à l\'application d\'utiliser le service IMS pour passer des appels sans votre intervention."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"Voir l\'état et l\'identité du téléphone"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permet à l\'application d\'accéder aux fonctionnalités téléphoniques de l\'appareil. Cette autorisation permet à l\'application de déterminer le numéro de téléphone et les identifiants de l\'appareil, si un appel est actif et le numéro distant connecté par un appel."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lire l\'état et l\'identité de téléphonie de base"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Autorise l\'appli à accéder aux fonctionnalités de téléphonie de base de l\'appareil."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"acheminer les appels via le système"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Autorise l\'application à acheminer les appels via le système afin d\'optimiser le confort d\'utilisation."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"voir et contrôler les appels via le système."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Appuyez pour supprimer votre empreinte faciale, puis ajoutez de nouveau votre visage"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurer le déverrouillage par reconnaissance faciale"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Déverrouillez votre téléphone en le regardant"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Pour utiliser Face Unlock, activez "<b>"Accès à l\'appareil photo"</b>" dans Paramètres &gt; Confidentialité"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configurer d\'autres méthodes de déverrouillage"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Appuyez pour ajouter une empreinte digitale"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Déverrouillage par empreinte digitale"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Préparation de <xliff:g id="APPNAME">%1$s</xliff:g> en cours…"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Lancement des applications…"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Finalisation de la mise à jour."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Éteindre l\'écran ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Vous avez appuyé sur le bouton Marche/Arrêt pendant la configuration de votre empreinte digitale.\n\nCela éteint généralement l\'écran."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Éteindre"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Annuler"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Poursuivre la configuration ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Vous avez appuyé sur le bouton Marche/Arrêt, ce qui éteint généralement l\'écran.\n\nEssayez d\'appuyer doucement pendant la configuration de votre empreinte digitale."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Éteindre l\'écran"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Poursuivre"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuer de valider votre empreinte ?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Vous avez appuyé sur le bouton Marche/Arrêt, ce qui éteint généralement l\'écran.\n\nPour valider votre empreinte digitale, appuyez plus doucement."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Éteindre l\'écran"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuer"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Appuyez pour revenir au jeu"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choisir un jeu"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index c07287a..6f9ee8f 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite que a aplicación use o servizo de IMS para facer chamadas sen a túa intervención."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ler o estado e a identidade do teléfono"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite á aplicación acceder ás funcións de teléfono do dispositivo. Con este permiso a aplicación pode determinar o número de teléfono e os ID do dispositivo, se unha chamada está activa e o número remoto conectado mediante unha chamada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ler a identidade e o estado das funcións básicas de telefonía"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permítelle á aplicación acceder ás funcións básicas de telefonía do dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"dirixir as chamadas a través do sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite á aplicación dirixir as súas chamadas a través do sistema para mellorar a túa experiencia durante as chamadas."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"consultar e controlar as chamadas a través do sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toca para eliminar o teu modelo facial e despois engade de novo a cara"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurar o desbloqueo facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Mira o teléfono para desbloquealo"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para usar o desbloqueo facial, activa "<b>"Acceso á cámara"</b>" en Configuración &gt; Privacidade"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configura máis maneiras de desbloquear o dispositivo"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Toca para engadir unha impresión dixital"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueo mediante impresión dixital"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Preparando <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Iniciando aplicacións."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Está finalizando o arranque"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Queres apagar a pantalla?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Premiches o botón de acendido mentres configurabas a impresión dixital.\n\nAo realizar esta acción, normalmente apágase a pantalla."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Apagar"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancelar"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Queres continuar coa configuración?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Premiches o botón de acendido, o que adoita facer que se apague a pantalla.\n\nProba a dar un toque suave namentres configuras a impresión dixital."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Desactivar pantalla"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Seguir configurando"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Queres seguir verificando a impresión?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Premiches o botón de acendido, o que adoita facer que se apague a pantalla.\n\nProba a dar un toque suave para verificar a impresión dixital."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Desactivar pantalla"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuar"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> está en execución"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Toca para volver ao xogo"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Escolle un xogo"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 5528954..533d3fb 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"તમારા હસ્તક્ષેપ વગર કૉલ્સ કરવા માટે IMS સેવાનો ઉપયોગ કરવાની એપ્લિકેશનને મંજૂરી આપે છે."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ફોન સ્થિતિ અને ઓળખ વાંચો"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"એપ્લિકેશનને ફોન સુવિધાઓને ઍક્સેસ કરવાની મંજૂરી આપે છે. આ પરવાનગી એપ્લિકેશનને ફોન નંબર અને ઉપકરણ ID, કૉલ સક્રિય છે અને કોઈ કૉલ દ્વારા કનેક્ટ થયેલ રિમોટ નંબર નિર્ધારિત કરવાની મંજૂરી આપે છે."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ટેલિફોન માટેનું મૂળભૂત સ્ટેટસ અને ઓળખ વાંચો"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ડિવાઇસની ટેલિફોન માટેની મૂળભૂત સુવિધાઓ ઍક્સેસ કરવાની મંજૂરી ઍપને આપે છે."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"સિસ્ટમ મારફતે કૉલ બીજે વાળો"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"કૉલિંગ અનુભવ સુધારવા માટે ઍપ્લિકેશનને સિસ્ટમ મારફતે કૉલ બીજે વાળવાની મંજૂરી આપે છે."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"સિસ્ટમ મારફતે કૉલ જુઓ અને નિયંત્રિત કરો."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"તમારા ચહેરાનું મૉડલ ડિલીટ કરવા માટે ટૅપ કરો, પછી તમારો ચહેરો ફરીથી ઉમેરો"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ફેસ અનલૉક સુવિધાનું સેટઅપ કરો"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"તમારા ફોનની તરફ જોઈને તેને અનલૉક કરો"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ફેસ અનલૉક સુવિધાનો ઉપયોગ કરવા માટે, સેટિંગ &gt; પ્રાઇવસીમાં જઈને "<b>"કૅમેરા ઍક્સેસ"</b>" ચાલુ કરો"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"અનલૉક કરવાની બીજી રીતોનું સેટઅપ કરો"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ફિંગરપ્રિન્ટ ઉમેરવા માટે ટૅપ કરો"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ફિંગરપ્રિન્ટ અનલૉક"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> તૈયાર કરી રહ્યું છે."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ઍપ્લિકેશનો શરૂ કરી રહ્યાં છે."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"બૂટ સમાપ્ત કરી રહ્યાં છે."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"સ્ક્રીન બંધ કરીએ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"તમારી ફિંગરપ્રિન્ટની સેટિંગનું સેટઅપ કરતી વખતે, તમે પાવર બટન દબાવ્યું.\n\nઆનાથી સામાન્ય રીતે તમારી સ્ક્રીન બંધ થઈ જાય છે."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"બંધ કરો"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"રદ કરો"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"શું સેટઅપ કરવાનું ચાલુ રાખીએ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"તમે પાવર બટન દબાવ્યું છે — જેનાથી સામાન્ય રીતે સ્ક્રીન બંધ થઈ જાય છે.\n\nતમારી ફિંગરપ્રિન્ટનું સેટઅપ કરતી વખતે હળવેથી ટૅપ કરવાનો પ્રયાસ કરો."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"સ્ક્રીન બંધ કરો"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"સેટઅપ આગળ વધારો"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"આગળ વધો"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ચાલુ છે"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ગેમ પર પાછા આવવા માટે ટૅપ કરો"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ગેમ પસંદ કરો"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index cb6de5c..8459437 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"आपके हस्‍तक्षेप के बिना कॉल करने के लिए, ऐप को IMS सेवा का उपयोग करने देती है."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"फ़ोन की स्‍थिति और पहचान पढ़ें"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ऐप्स को डिवाइस की फ़ोन सुविधाओं तक पहुंचने देता है. यह अनुमति ऐप्स  को फ़ोन नंबर और डिवाइस आईडी, कॉल सक्रिय है या नहीं, और कॉल द्वारा कनेक्ट किया गया दूरस्‍थ नंबर तय करने देती है."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"टेलिफ़ोनी सुविधाओं की सामान्य स्थिति और उनकी पहचान से जुड़ी जानकारी पढ़ें"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ऐसा करने पर, इस ऐप्लिकेशन को डिवाइस की सामान्य टेलिफ़ोनी सुविधाएं ऐक्सेस करने की अनुमति मिलती है."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"सिस्टम के माध्यम से कॉल रूट करें"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"कॉल करने के अनुभव को बेहतर बनाने के लिए ऐप्लिकेशन को सिस्टम के माध्यम से उसके कॉल रूट करने देती है."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"सिस्टम के ज़रिए कॉल देखना और नियंत्रित करना."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"अपने चेहरे का मॉडल मिटाने के लिए टैप करें. इसके बाद, अपना चेहरा फिर से रजिस्टर करें"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"फे़स अनलॉक की सुविधा सेट अप करें"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"अपने फ़ोन की तरफ़ देखकर उसे अनलॉक करें"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"फ़ेस अनलॉक की सुविधा का इस्तेमाल करने के लिए, सेटिंग और निजता में जाकर, "<b>"कैमरे का ऐक्सेस"</b>" चालू करें"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"फ़ोन को अनलॉक करने के दूसरे तरीके सेट अप करें"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"फ़िंगरप्रिंट जोड़ने के लिए टैप करें"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"फ़िंगरप्रिंट अनलॉक"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> तैयार हो रहा है."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ऐप्स  प्रारंभ होने वाले हैं"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"बूट खत्म हो रहा है."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"स्क्रीन बंद करें?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"अपना फ़िंगरप्रिंट सेट अप करते समय, आपने पावर बटन दबाया.\n\nआम तौर पर, इससे स्क्रीन बंद हो जाती है."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"बंद करें"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"अभी नहीं"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"सेट अप जारी रखना है?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"आपने पावर बटन दबाया - आम तौर पर, इससे स्क्रीन बंद हो जाती है.\n\nअपना फ़िंगरप्रिंट सेट अप करते समय, बटन को हल्के से टैप करके देखें."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"स्क्रीन बंद करें"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"सेट अप जारी रखें"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"जारी रखें"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> चल रही है"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"गेम पर वापस जाने के लिए टैप करें"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"गेम चुनें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 1dc2517..69905ec 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -474,6 +474,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Omogućuje aplikaciji upotrebu usluge izravnih poruka za uspostavljanje poziva bez vaše intervencije."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"čitanje statusa i identiteta telefona"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Aplikaciji omogućuje pristup telefonskim značajkama uređaja. Ta dozvola aplikaciji omogućuje utvrđivanje telefonskog broja i ID-ova uređaja, je li poziv aktivan te udaljeni broj koji je povezan pozivom."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"čitanje osnovnih telefonskih statusa i identiteta"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Dopušta aplikacijama pristup osnovnim telefonskim značajkama uređaja."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"preusmjeravati pozive putem sustava"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Omogućuje aplikaciji da preusmjerava pozive putem sustava radi poboljšanja doživljaja."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"vidjeti i kontrolirati pozive putem sustava."</string>
@@ -625,6 +627,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Dodirnite da biste izbrisali model lica, a zatim ponovo dodajte svoje lice"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Postavite otključavanje licem"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Otključajte telefon gledajući u njega"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Da biste koristili otključavanje licem, uključite opciju "<b>"Pristup kameri"</b>" u odjeljku Postavke &gt; Privatnost"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Postavite više načina otključavanja"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Dodirnite da biste dodali otisak prsta"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Otključavanje otiskom prsta"</string>
@@ -1296,10 +1299,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Pripremanje aplikacije <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Pokretanje aplikacija."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Završetak inicijalizacije."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Isključiti zaslon?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Prilikom postavljanja otiska prsta pritisnuli ste tipku za uključivanje/isključivanje.\n\nTom se radnjom obično isključuje zaslon."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Isključi"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Odustani"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Želite li nastaviti s postavljanjem?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pritisnuli ste tipku za uključivanje/isključivanje, čime se obično isključuje zaslon.\n\nPokušajte lagano dodirnuti dok postavljate svoj otisak prsta."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Isključi zaslon"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Nastavi postavljanje"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Nastaviti s potvrđivanjem otiska prsta?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pritisnuli ste tipku za uključivanje/isključivanje, čime se obično isključuje zaslon.\n\nPokušajte lagano dodirnuti da biste potvrdili svoj otisak prsta."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Isključi zaslon"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Nastavi"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Izvodi se <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Dodirnite za povratak na igru"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Odabir igre"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 34f9963..4f04016 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Az alkalmazás az IMS-szolgáltatást használhatja híváskezdeményezéshez az Ön közbeavatkozása nélkül."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"telefonállapot és azonosító olvasása"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Lehetővé teszi az alkalmazás számára, hogy hozzáférjen az eszköz telefonálási funkcióihoz. Az engedéllyel rendelkező alkalmazás meghatározhatja a telefonszámot és eszközazonosítókat, hogy egy hívás aktív-e, valamint híváskor a másik fél telefonszámát."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"alapvető telefonállapot és identitás olvasása"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Lehetővé teszi az alkalmazás számára, hogy hozzáférjen az eszköz alapvető telefonos szolgáltatásaihoz."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"rendszeren keresztüli hívásirányítás"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"A telefonálási élmény javítása érdekében lehetővé teszi az alkalmazás számára a rendszeren keresztüli hívásirányítást."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Hívások megtekintése és vezérlése a rendszeren keresztül"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Koppintson arcmodellje törléséhez, majd készítsen újat"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Az Arcalapú feloldás beállítása"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Feloldhatja a zárolást úgy, hogy ránéz a telefonra"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Az Arcalapú feloldás funkció használatához kapcsolja be a "<b>"Hozzáférés a kamerához"</b>" beállítást a Beállítások &gt; Adatvédelem szakaszban."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"További feloldási módszerek beállítása"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Koppintson ide ujjlenyomat hozzáadásához"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Feloldás ujjlenyomattal"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> előkészítése."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Kezdő alkalmazások."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Rendszerindítás befejezése."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Kikapcsolja a képernyőt?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Ujjlenyomata beállítása közben megnyomta a bekapcsológombot.\n\nEz általában kikapcsolja a képernyőt."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Kikapcsolás"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Mégse"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Folytatja a beállítást?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Megnyomta a bekapcsológombot — ezzel általában kikapcsol a képernyő.\n\nPróbáljon finoman rákoppintani az ujjlenyomat beállítása közben."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Kikapcsolom"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Beállítás folytatása"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Folytatja az ujjlenyomat ellenőrzését?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Megnyomta a bekapcsológombot — ezzel általában kikapcsol a képernyő.\n\nPróbáljon finoman rákoppintani az ujjlenyomat ellenőrzéséhez."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Kikapcsolom"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Tovább"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> fut"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Koppintson ide a játékhoz való visszatéréshez"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Játék kiválasztása"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 03f9e1d..141780e 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Թույլ է տալիս հավելվածին IMS ծառայության միջոցով կատարել զանգեր՝ առանց ձեր միջամտության:"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"կարդալ հեռախոսի կարգավիճակը և ինքնությունը"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Թույլ է տալիս հավելվածին օգտագործել սարքի հեռախոսային գործիքները: Այս թույլտվությունը հավելվածին հնարավորություն է տալիս որոշել հեռախոսահամարը և սարքի ID-ները, արդյոք զանգը ակտիվ է և միացված զանգի հեռակա հեռախոսահամարը:"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"Կարդալ հիմնական հեռախոսային գործառույթների կարգավիճակը և նույնականացնող տվյալներ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Հավելվածին հասանելի է դարձնում սարքի հիմնական հեռախոսային գործառույթները:"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"զանգերն ուղարկել համակարգի միջոցով"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Հավելվածին թույլ է տալիս իր զանգերն ուղարկել համակարգի միջոցով՝ կապի որակը բարձրացնելու նպատակով։"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Զանգերի դիտում և վերահսկում համակարգի միջոցով"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Հպեք՝ ձեր դեմքի նմուշը ջնջելու համար, այնուհետև նորից ավելացրեք այն:"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Կարգավորեք դեմքով ապակողպումը"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Ապակողպելու համար պարզապես նայեք հեռախոսին"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Դեմքով ապակողպումն օգտագործելու համար անցեք Կարգավորումներ &gt; Գաղտնիություն և տրամադրեք "<b>"տեսախցիկն օգտագործելու թույլտվություն"</b>"։"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Կարգավորեք ապակողպելու այլ եղանակներ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Հպեք՝ մատնահետք ավելացնելու համար"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Մատնահետքով ապակողպում"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> հավելվածը պատրաստվում է:"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Հավելվածները մեկնարկում են:"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Բեռնումն ավարտվում է:"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Անջատե՞լ էկրանը"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Մատնահետքը կարգավորելու ժամանակ դուք սեղմել եք սնուցման կոճակը։\n\nՍովորաբար դրա արդյունքում էկրանն անջատվում է։"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Անջատել"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Չեղարկել"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Շարունակե՞լ կարգավորումը"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Դուք սեղմել եք սնուցման կոճակը։ Սովորաբար դրա արդյունքում էկրանն անջատվում է։\n\nՄատնահետքը ավելացնելու համար թեթևակի հպեք կոճակին։"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Անջատել էկրանը"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Շարունակել գրանցումը"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Շարունակել"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g>-ն աշխատում է"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Հպեք՝ խաղին վերադառնալու համար"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Ընտրեք խաղ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index db97591..e1e8ea9 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Memungkinkan aplikasi menggunakan layanan IMS untuk melakukan panggilan tanpa campur tangan Anda."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"baca identitas dan status ponsel"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Memungkinkan aplikasi mengakses fitur telepon perangkat. Izin ini memungkinkan aplikasi menentukan nomor telepon dan ID perangkat, apakah suatu panggilan aktif atau tidak, dan nomor jarak jauh yang terhubung oleh sebuah panggilan."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"membaca identitas dan status telepon dasar"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Memungkinkan aplikasi mengakses fitur telepon dasar perangkat."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"sambungkan panggilan telepon melalui sistem"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Mengizinkan aplikasi menyambungkan panggilan telepon melalui sistem untuk menyempurnakan pengalaman menelepon."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"melihat dan mengontrol panggilan melalui sistem."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Ketuk untuk menghapus model wajah, lalu tambahkan wajah Anda lagi"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Siapkan Face Unlock"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Buka kunci ponsel dengan melihat ke ponsel"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Untuk menggunakan Face Unlock, aktifkan "<b>"Akses kamera"</b>" di Setelan &gt; Privasi"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Siapkan lebih banyak cara untuk membuka kunci"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Ketuk untuk menambahkan sidik jari"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingerprint Unlock"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Menyiapkan <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Memulai aplikasi."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Menyelesaikan boot."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Nonaktifkan layar?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Saat menyiapkan sidik jari, Anda menekan Tombol daya.\n\nTindakan ini biasanya akan menonaktifkan layar."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Nonaktifkan"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Batal"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Lanjutkan penyiapan?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Anda menekan tombol daya; tindakan ini biasanya akan menonaktifkan layar.\n\nCoba ketuk lembut saat menyiapkan sidik jari Anda."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Nonaktifkan layar"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Lanjutkan penyiapan"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Lanjutkan verifikasi sidik jari?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Anda menekan tombol daya; tindakan ini biasanya akan menonaktifkan layar.\n\nCoba ketuk lembut untuk memverifikasi sidik jari Anda."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Nonaktifkan layar"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Lanjutkan"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> sedang berjalan"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Ketuk untuk kembali ke game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Pilih game"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index d70a66b..0678ab2 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Leyfir forriti að nota IMS-þjónustu til að hringja án inngrips frá þér."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lesa stöðu símans og auðkenni"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Leyfir forriti að fá aðgang að símaeiginleikum tækisins. Þessi heimild gerir forritinu kleift að komast að símanúmeri og auðkennum tækisins, hvort símtal er í gangi og símanúmeri viðmælanda í símtali."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lesa stöðu og auðkenni grunneiginleika símaþjónustu"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Leyfir forritinu að fá aðgang að grunneiginleikum símaþjónustu tækisins."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"senda símtöl gegnum kerfið"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Heimilar forritinu að senda símtöl sín gegnum kerfið til að bæta gæði símtalsins."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"sjá og stjórna símtölum í gegnum kerfið."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Ýttu til að eyða andlitslíkaninu og skráðu svo andlitið aftur"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Setja upp andlitskenni"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Taktu símann úr lás með því að horfa á hann"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Þú verður að kveikja á "<b>"aðgangi að myndavél"</b>" í „Stillingar &gt; persónuvernd“ til að nota andlitskenni"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Settu upp fleiri leiðir til að taka úr lás"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Ýttu til að bæta við fingrafari"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingrafarskenni"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Undirbýr <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Ræsir forrit."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Lýkur ræsingu."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Slökkva á skjá?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Þú ýttir á aflrofann þegar þú varst að skrá fingrafarið þitt.\n\nYfirleitt slekkur það á skjánum."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Slökkva"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Hætta við"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Halda uppsetningu áfram?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Þú ýttir á aflrofann. Yfirleitt slekkur það á skjánum.\n\nPrófaðu að ýta laust þegar þú setur upp fingrafarið."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Slökkva á skjá"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Halda uppsetningu áfram"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Halda áfram að staðfesta fingrafarið?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Þú ýttir á aflrofann. Yfirleitt slekkur það á skjánum.\n\nPrófaðu að ýta laust til að staðfesta fingrafarið þitt."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Slökkva á skjá"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Áfram"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> er í gangi"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Ýttu til að fara aftur í leik"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Velja leik"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 91df4bd..a63fbd8 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Consente all\'app di utilizzare il servizio IMS per fare chiamate senza il tuo intervento."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lettura stato e identità telefono"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Consente all\'applicazione di accedere alle funzioni telefoniche del dispositivo. Questa autorizzazione consente all\'applicazione di determinare il numero di telefono e gli ID dei dispositivi, se una chiamata è attiva e il numero remoto connesso da una chiamata."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lettura di identità e stato telefonia"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Consente all\'app di accedere alle funzionalità di telefonia di base del dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"indirizzamento delle chiamate tramite il sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Consente all\'app di indirizzare le proprie chiamate tramite il sistema al fine di migliorare l\'esperienza di chiamata."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"visualizzazione e controllo delle chiamate tramite il sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tocca per eliminare il tuo modello del volto e poi riaggiungi il tuo volto"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configura lo sblocco con il volto"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Sblocca il telefono guardandolo"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Per utilizzare lo sblocco con il volto, attiva "<b>"l\'accesso alla fotocamera"</b>" in Impostazioni &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configura altri modi per sbloccare"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tocca per aggiungere un\'impronta"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Sblocco con l\'impronta"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> in preparazione."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Avvio app."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Conclusione dell\'avvio."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vuoi disattivare lo schermo?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Durante la configurazione della tua impronta hai premuto il tasto di accensione.\n\nGeneralmente questa azione comporta la disattivazione dello schermo."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Disattiva"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Annulla"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Vuoi continuare la configurazione?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Hai premuto il tasto di accensione; in genere questa azione disattiva lo schermo.\n\nProva a toccare leggermente il tasto di accensione durante la configurazione della tua impronta."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Disattiva lo schermo"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continua configuraz."</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Vuoi continuare a verificare l\'impronta?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Hai premuto il tasto di accensione; in genere questa azione disattiva lo schermo.\n\nProva a toccare leggermente il tasto di accensione per verificare la tua impronta."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Disattiva lo schermo"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continua"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tocca per tornare al gioco"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Scegli gioco"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index a16c682..7a594d8 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"‏מאפשרת לאפליקציה להשתמש בשירות ה-IMS לביצוע שיחות ללא התערבות שלך."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"קריאת הסטטוס והזהות של הטלפון"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"מאפשרת לאפליקציה לגשת לתכונות הטלפון של המכשיר. ההרשאה הזו מתירה לאפליקציה לגלות את מספר הטלפון ואת מזהי המכשיר, אם השיחה פעילה ואת המספר המרוחק המחובר באמצעות שיחה."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"קריאה של זהות וסטטוס טלפוניים בסיסיים"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ההרשאה מאפשרת לאפליקציה לגשת לתכונות הטלפוניות הבסיסיות של המכשיר."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ניתוב שיחות דרך המערכת"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"מאפשרת לאפליקציה לנתב את השיחות דרך המערכת כדי לשפר את חוויית השיחה."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"הצגת שיחות ושליטה בהן באמצעות המערכת."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"יש להקיש כדי למחוק את התבנית לזיהוי הפנים, ואז להוסיף תבנית חדשה לזיהוי הפנים"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"הגדרת התכונה \'פתיחה ע\"י זיהוי הפנים\'"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"יש להביט בטלפון כדי לבטל את נעילתו"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"‏כדי להשתמש בתכונה \'פתיחה ע\"י זיהוי הפנים\', יש להפעיל את ה"<b>"גישה למצלמה"</b>" בהגדרות &gt; פרטיות"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"אפשר להגדיר דרכים נוספות לביטול נעילה"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"יש להקיש כדי להוסיף טביעת אצבע"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ביטול הנעילה בטביעת אצבע"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"המערכת מכינה את <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"מתבצעת הפעלה של אפליקציות."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"תהליך האתחול בשלבי סיום."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"לכבות את המסך?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"כשהגדרת את טביעת האצבע, לחצת על לחצן ההפעלה.\n\nלרוב, הפעולה הזו מכבה את המסך."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"כיבוי"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ביטול"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"להמשיך בהגדרה?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"לחצת על לחצן ההפעלה – בדרך כלל הפעולה הזו מכבה את המסך.\n\nעליך לנסות להקיש בעדינות במהלך ההגדרה של טביעת האצבע שלך."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"כיבוי המסך"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"להמשך ההגדרה"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"המשך"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"האפליקציה <xliff:g id="APP">%1$s</xliff:g> פועלת"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"יש להקיש כדי לחזור למשחק"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"בחירת משחק"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index ff7a868..241105f 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"IMSサービスがユーザー操作なしで電話をかけることをアプリに許可します。"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"デバイス情報と ID の読み取り"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"デバイスの電話機能へのアクセスをアプリに許可します。これにより、電話番号、デバイスID、通話中かどうか、通話相手の電話番号をアプリから特定できるようになります。"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"電話の基本的なステータスと ID の読み取り"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"デバイスの基本的な電話機能へのアクセスをアプリに許可します。"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"システム経由での通話転送"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"通話環境の改善のために、システム経由での通話転送をアプリに許可します。"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"システム経由の通話の表示と操作。"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"タップして顔モデルを削除してから、改めて顔を追加してください"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"顔認証の設定"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"スマートフォンに顔を向けるとロックが解除されます"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"顔認証を使用するには、[設定] &gt; [プライバシー] で"<b>"カメラへのアクセス"</b>"を有効にしてください"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"その他のロック解除方法の設定"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"タップすると指紋が追加されます"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"指紋認証"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g>をペア設定しています。"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"アプリを起動しています。"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ブートを終了しています。"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"画面を OFF にしますか?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"指紋の設定中に電源ボタンが押されました。\n\n通常、この操作により画面が OFF になります。"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"OFF にする"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"キャンセル"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"設定を続行しますか?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"電源ボタンを押しました。通常、この操作で画面が OFF になります。\n\n指紋を設定する場合は電源ボタンに軽く触れてみましょう。"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"画面を OFF にする"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"設定を続行"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"指紋の確認を続行しますか?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"電源ボタンを押しました。通常、この操作で画面が OFF になります。\n\n指紋を確認するには、電源ボタンに軽く触れてみましょう。"</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"画面を OFF にする"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"続行"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g>を実行中"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"タップするとゲームに戻ります"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ゲームの選択"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index dbc7bf59..2137e0f 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"აპს შეეძლება, გამოიყენოს IMS სერვისი ზარების თქვენი ჩარევის გარეშე განსახორციელებლად."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ტელეფონის სტატუსისა და იდენტობის წაკითხვა"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"აპს შეეძლება ჰქონდეს წვდომა მოწყობილობის სატელეფონო ფუნქციებზე. აპმა მსგავსი უფლებით შეძლებს დაადგინოს ტელეფონის ნომერი, მისი სერიული გამოცემა, აქტიური ზარი, დაკავშირებული ნომერი და მსგავსი."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"წაიკითხეთ ტელეფონის ძირითადი სტატუსი და აიდი"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"აპს აძლევს მოწყობილობის ძირითად სატელეფონო ფუნქციებზე წვდომას."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ზარების სისტემის მეშვეობით მარშრუტიზაცია"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"დარეკვის ხარისხის გაუმჯობესების მიზნით, აპს ზარების სისტემის მეშვეობით მარშრუტიზაციის საშუალებას აძლევს."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ზარების ნახვა და გაკონტროლება სისტემის მეშვეობით."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"შეეხეთ თქვენი სახის მოდელის წასაშლელად, შემდეგ დაამატეთ სახე ხელახლა"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"სახით განბლოკვის პარამეტრების დაყენება"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"განბლოკეთ თქვენი ტელეფონი შეხედვით"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"იმისთვის, რომ სახით განბლოკვით ისარგებლოთ, ჩართეთ "<b>"კამერაზე წვდომა"</b>" პარამეტრებსა და კონფიდენციალურობაში"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"დააყენეთ განბლოკვის სხვა ხერხები"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"შეეხეთ თითის ანაბეჭდის დასამატებლად"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"თითის ანაბეჭდით განბლოკვა"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"ემზადება <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"აპების ჩართვა"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ჩატვირთვის დასასრული."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"გამოირთოს ეკრანი?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"თითის ანაბეჭდის დაყენებისას ჩართვის ღილაკს დააჭირეთ.\n\nეს, ჩვეულებრივ, თქვენს ეკრანს გამორთავს."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"გამორთვა"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"გაუქმება"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"გსურთ დაყენების გაგრძელება?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"თქვენ დააჭირეთ ჩართვის ღილაკს — ჩვეულებრივ, ის ეკრანს გამორთავს.\n\nთქვენი თითის ანაბეჭდის დაყენებისას ცადეთ მსუბუქად შეხება."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ეკრანის გამორთვა"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"დაყენების გაგრძელება"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"გაგრძელება"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> გაშვებულია"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"შეეხეთ თამაშში დასაბრუნებლად"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"აირჩიეთ თამაში"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index c1d421f..3042c64 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Сіздің қатысуыңызсыз қоңыраулар соғу үшін қолданбаға IMS қызметін пайдалануға рұқсат етеді."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"телефон күйін оқу немесе анықтау"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Қолданбаға құрылғыдағы телефон функцияларына кіру мүмкіндігін береді. Бұл рұқсат қолданбаға телефон нөмірі, құрылғы жеке анықтағышы, қоңырау белсенділігі және сол қоңырауға байланысты қашықтағы нөмірді анықтау мүмкіндігін береді."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"негізгі телефония күйі мен сәйкестендіру деректерін оқу"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Қолданбаға құрылғының негізгі телефония функцияларын пайдалануға рұқсат береді."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"қоңырауларды жүйе арқылы бағыттау"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Қоңырау шалу тәжірибесін жақсарту үшін қолданба қоңырауларды жүйе арқылы бағыттай алады."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"жүйе арқылы қоңырауларды көру және басқару."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Бет үлгісін жою үшін түртіңіз, содан соң жаңа бет үлгісін қосыңыз."</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Бет тану функциясын реттеу"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Телефоныңызға қарап, оның құлпын ашыңыз."</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Face Unlock функциясын пайдалану үшін \"Параметрлер &gt; Құпиялылық\" бөлімінен "<b>"Камераны пайдалану рұқсатын"</b>" қосыңыз."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Құлыпты ашудың басқа тәсілдерін реттеу"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Саусақ ізін қосу үшін түртіңіз."</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Құлыпты саусақ ізімен ашу"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> дайындалуда."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Қолданбалар іске қосылуда."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Қосуды аяқтауда."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Экранды өшіру керек пе?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Саусақ ізіңізді орнату кезінде қуат түймесін басып қалдыңыз.\n\nБұл әрекет әдетте экранды өшіреді."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Өшіру"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Бас тарту"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Реттеуді жалғастырасыз ба?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Қуат түймесін бастыңыз. Бұл әдетте экранды өшіреді.\n\nСаусақ ізін реттеу үшін, оны жайлап түртіп көріңіз."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Экранды өшіру"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Реттеуді жалғастыру"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Жалғастыру"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> қосылған"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Ойынды жалғастыру үшін түртіңіз"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Ойынды таңдаңыз"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 2889ac3..81fbca5 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"អនុញ្ញាតឲ្យកម្មវិធីនេះប្រើសេវាកម្ម IMS ដើម្បីធ្វើការហៅដោយគ្មានការអន្តរាគមន៍ពីអ្នក។"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"អាន​ស្ថានភាព និង​អត្តសញ្ញាណ​ទូរស័ព្ទ"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ឲ្យ​កម្មវិធី​ចូល​ដំណើរការ​លក្ខណៈ​ទូរស័ព្ទ​នៃ​ឧបករណ៍។ សិទ្ធិ​នេះ​​ឲ្យ​កម្មវិធី​កំណត់​លេខ​ទូរស័ព្ទ និង​លេខ​សម្គាល់​ឧបករណ៍ ថា​តើ​ការ​ហៅ​សកម្ម និង​លេខ​ពី​ចម្ងាយ​បាន​ភ្ជាប់​ដោយ​ការ​ហៅ។"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"អានអត្តសញ្ញាណ និងស្ថានភាពទូរសព្ទគោល"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"អនុញ្ញាតឱ្យកម្មវិធីចូលប្រើមុខងារទូរសព្ទគោលរបស់ឧបករណ៍។"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"បញ្ជូន​ការ​ហៅ​ទូរសព្ទ​តាមរយៈ​ប្រព័ន្ធ"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"អនុញ្ញាត​ឲ្យ​កម្មវិធី​នេះ​បញ្ជូន​ការ​ហៅ​ទូរសព្ទ​របស់វា​តាមរយៈ​ប្រព័ន្ធ ​ដើម្បី​ធ្វើ​ឲ្យ​ការ​ហៅ​ទូរសព្ទ​ប្រសើរ​ជាង​មុន។"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"មើល និង​គ្រប់គ្រង​ការហៅទូរសព្ទ​តាមរយៈប្រព័ន្ធ។"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ចុចដើម្បីលុប​គំរូមុខ​របស់អ្នក រួចបញ្ចូល​មុខរបស់អ្នក​ម្ដងទៀត"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"រៀបចំ​ការដោះសោ​តាមទម្រង់មុខ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ដោះសោទូរសព្ទ​របស់អ្នកដោយសម្លឹងមើលវា"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ដើម្បីប្រើមុខងារដោះសោតាមទម្រង់មុខ សូមបើក"<b>"ការចូលប្រើកាមេរ៉ា"</b>"នៅក្នុងការកំណត់ &gt; ឯកជនភាព"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"រៀបចំ​វិធីច្រើនទៀត​ដើម្បី​ដោះសោ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ចុច​ដើម្បីបញ្ចូល​ស្នាមម្រាមដៃ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ការដោះសោ​ដោយប្រើ​ស្នាមម្រាមដៃ"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"កំពុងរៀបចំ <xliff:g id="APPNAME">%1$s</xliff:g>។"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ចាប់ផ្ដើម​កម្មវិធី។"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"បញ្ចប់​ការ​ចាប់ផ្ដើម។"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"បិទ​អេក្រង់ឬ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"នៅពេលរៀបចំ​ស្នាមម្រាមដៃ​របស់អ្នក អ្នកបានចុច​ប៊ូតុងថាមពល។\n\nជាធម្មតា ការធ្វើបែបនេះ​បិទអេក្រង់​របស់អ្នក។"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"បិទ"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"បោះបង់"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"បន្ត​រៀបចំឬ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"អ្នកបានចុចប៊ូតុងថាមពល — ជាធម្មតាការធ្វើបែបនេះនឹងបិទអេក្រង់។\n\nសាកល្បងចុចថ្នមៗ ខណៈពេលកំពុងរៀបចំស្នាមម្រាមដៃរបស់អ្នក។"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"បិទ​អេក្រង់"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"បន្ត​រៀបចំ"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"បន្ត"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> កំពុង​ដំណើរការ"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ចុច​ដើម្បី​ត្រឡប់​ទៅ​ហ្គេមវិញ"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ជ្រើសរើស​ហ្គេម"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index cc9771a..569f6a5 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ನಿಮ್ಮ ಮಧ್ಯಸ್ಥಿಕೆ ಇಲ್ಲದೆಯೇ ಕರೆಗಳನ್ನು ಮಾಡಲು IMS ಸೇವೆಯನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ಫೋನ್ ಸ್ಥಿತಿ ಮತ್ತು ಗುರುತಿಸುವಿಕೆಯನ್ನು ಓದಿ"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ಸಾಧನದ ಫೋನ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಫೋನ್ ಸಂಖ್ಯೆ ಮತ್ತು ಸಾಧನದ ID ಗಳನ್ನು ನಿರ್ಧರಿಸಲು, ಕರೆಯು ಸಕ್ರಿಯವಾಗಿದೆಯೇ ಮತ್ತು ಕರೆಯ ಮೂಲಕ ರಿಮೋಟ್ ಸಂಖ್ಯೆಯು ಸಂಪರ್ಕಗೊಂಡಿವೆಯೇ ಎಂಬುದನ್ನೂ ನಿರ್ಧರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಕಲ್ಪಿಸುತ್ತದೆ."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ಮೂಲ ಟೆಲಿಫೋನ್ ಸ್ಥಿತಿ ಮತ್ತು ಗುರುತನ್ನು ಓದಿ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ಸಾಧನದ ಮೂಲ ಟೆಲಿಫೋನ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ಕರೆಗಳನ್ನು ಸಿಸ್ಟಂ ಮೂಲಕ ರವಾನಿಸಿ"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"ಕರೆಯ ಅನುಭವವನ್ನು ಸುಧಾರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ನ ಕರೆಗಳನ್ನು ಸಿಸ್ಟಂ ಮೂಲಕ ರವಾನಿಸಲು ಅನುಮತಿಸುತ್ತದೆ."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ಸಿಸ್ಟಂ ಮೂಲಕ ಕರೆಗಳನ್ನು ವೀಕ್ಷಿಸಿ ಮತ್ತು ನಿಯಂತ್ರಿಸಿ."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ನಿಮ್ಮ ಫೇಸ್ ಮಾಡೆಲ್ ಅನ್ನು ಅಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ, ನಂತರ ನಿಮ್ಮ ಫೇಸ್ ಮಾಡೆಲ್ ಅನ್ನು ಪುನಃ ಸೇರಿಸಿ"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ಫೇಸ್ ಅನ್‌ಲಾಕ್ ಅನ್ನು ಸೆಟಪ್ ಮಾಡಿ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ಫೋನ್ ಅನ್ನು ನೋಡುವ ಮೂಲಕ ಅನ್‌ಲಾಕ್‌ ಮಾಡಿ"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ಫೇಸ್ ಅನ್‌ಲಾಕ್ ಬಳಸಲು, ಸೆಟ್ಟಿಂಗ್‌ಗಳು &gt; ಗೌಪ್ಯತೆ ಎಂಬಲ್ಲಿ "<b>"ಕ್ಯಾಮರಾ ಪ್ರವೇಶವನ್ನು"</b>" ಆನ್ ಮಾಡಿ"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಹೆಚ್ಚಿನ ಮಾರ್ಗಗಳನ್ನು ಹೊಂದಿಸಿ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್ ಸೇರಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಅನ್‌ಲಾಕ್"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> ಸಿದ್ಧಪಡಿಸಲಾಗುತ್ತಿದೆ."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ಬೂಟ್ ಪೂರ್ಣಗೊಳಿಸಲಾಗುತ್ತಿದೆ."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ಸ್ಕ್ರೀನ್ ಆಫ್ ಮಾಡಬೇಕೇ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್ ಅನ್ನು ಸೆಟ್ಟಪ್ ಮಾಡುವಾಗ ನೀವು ಪವರ್ ಬಟನ್‌ಅನ್ನು ಒತ್ತಿದ್ದೀರಿ \n\nಸಾಮಾನ್ಯವಾಗಿ ಇದರಿಂದ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಆಫ್ ಆಗುತ್ತದೆ."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ಆಫ್ ಮಾಡಿ"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ರದ್ದುಗೊಳಿಸಿ"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ಸೆಟಪ್ ಮುಂದುವರಿಸಬೇಕೆ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ನೀವು ಪವರ್ ಬಟನ್ ಒತ್ತಿದ್ದೀರಿ — ಇದು ಸಾಮಾನ್ಯವಾಗಿ ಸ್ಕ್ರೀನ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ.\n\nನಿಮ್ಮ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್ ಅನ್ನು ಹೊಂದಿಸುವಾಗ ಲಘುವಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಲು ಪ್ರಯತ್ನಿಸಿ."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ಸ್ಕ್ರೀನ್ ಆಫ್ ಮಾಡಿ"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ಸೆಟಪ್ ಮುಂದುವರಿಸಿ"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ಮುಂದುವರಿಸಿ"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ರನ್ ಆಗುತ್ತಿದೆ"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ಆಟಕ್ಕೆ ಹಿಂತಿರುಗಲು ಟ್ಯಾಪ್‌ ಮಾಡಿ"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ಆಟವನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index aacc58b..01d040f 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"앱이 IMS 서비스를 사용하여 자동으로 전화를 걸 수 있도록 허용합니다."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"휴대전화 상태 및 ID 읽기"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"앱이 기기의 휴대전화 기능에 접근할 수 있도록 허용합니다. 이 권한을 사용하면 앱이 전화번호 및 기기의 ID, 활성 통화인지 여부, 통화가 연결된 원격 번호 등을 확인할 수 있습니다."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"기본 전화 상태 및 ID 읽기"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"앱이 기기의 기본 전화 기능에 액세스할 수 있도록 허용합니다."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"시스템을 통해 통화 연결"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"통화 환경을 개선하기 위해 앱이 시스템을 통해 통화를 연결하도록 허용합니다."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"시스템을 통해 통화 확인 및 제어"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"탭하여 얼굴 모델을 삭제한 후 다시 얼굴을 추가하세요"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"얼굴 인식 잠금 해제 설정"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"휴대전화의 화면을 응시하여 잠금 해제할 수 있습니다."</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"얼굴 인식 잠금 해제를 사용하려면 설정 &gt; 개인 정보 보호에서 "<b>"카메라 액세스"</b>"를 사용 설정하세요."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"다른 잠금 해제 방법 설정"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"지문을 추가하려면 탭하세요."</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"지문 잠금 해제"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> 준비 중..."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"앱을 시작하는 중입니다."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"부팅 완료"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"화면을 끄시겠습니까?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"지문을 설정하는 중에 전원 버튼이 눌렸습니다.\n\n이렇게 하면 보통 화면이 꺼집니다."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"끄기"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"취소"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"설정을 계속하시겠어요?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"전원 버튼을 눌렀습니다. 이러면 보통 화면이 꺼집니다.\n\n지문 설정 중에 가볍게 탭하세요."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"화면 끄기"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"설정 계속"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"계속"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"게임으로 돌아가려면 탭하세요."</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"게임 선택"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 5399b4c..e153773 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Колдонмого сизди катыштырбай туруп, IMS кызматынын жардамы менен, чалууларды жасоо мүмкүнчүлүгүн берет."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"телефондун абалын жана аныктыгын окуу"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Колдонмого түзмөктүн чалуу мүмкүнчүлүктөрүнө жетки алуу уруксатын берет. Бул уруксат колдонмого, телефондун номурун, түзмөктүн ID-син, чалуунун абалын жана байланышта чыккан номурду аныктоого жол берет."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"негизги телефония статусун окуу жана аныктоо"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Колдонмого түзмөктүн негизги телефония функцияларын колдонууга мүмкүнчүлүк берет."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"чалууларды тутум аркылуу өткөрүү"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Чалуунун сапатын жакшыртуу максатында колдонмого чалууларын тутум аркылуу өткөрүүгө уруксат берет."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"тутум аркылуу чалууларды көрүп, көзөмөлдөө."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Жүзүңүздүн үлгүсүн өчүрүү үчүн басып, жаңы үлгүнү кошуңуз"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Жүзүнөн таанып ачууну жөндөө"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Телефонуңузду карап туруп эле кулпусун ачып алыңыз"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Жүзүнөн таанып ачуу функциясын колдонуу үчүн Жөндөөлөр &gt; Купуялык бөлүмүнө өтүп, "<b>"Камераны колдонууну"</b>" күйгүзүңүз"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Кулпусун ачуунун көбүрөөк жолдорун жөндөңүз"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Манжа изин кошуу үчүн басыңыз"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Кулпуланган түзмөктү манжа изи менен ачуу"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> даярдалууда."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Колдонмолорду иштетип баштоо"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Жүктөлүүдө"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Экран өчүрүлсүнбү?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Манжаңыздын изин жөндөп жатканда күйгүзүү/өчүрүү баскычын басып алдыңыз.\n\nБул адатта экранды өчүрөт."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Өчүрүү"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Жок"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Жөндөөнү улантасызбы?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Кубат баскычын бастыңыз — адатта, бул экранды өчүрөт.\n\nМанжаңыздын изин жөндөп жатканда аны акырын басып көрүңүз."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Экранды өчүрүү"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Жөндөөнү улантуу"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Улантуу"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> иштеп жатат"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Оюнга кайтуу үчүн таптаңыз"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Оюн тандоо"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 88de60d..b3cb5af 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ອະ​ນຸ​ຍາດ​ໃຫ້​ແອັບ​ໃຊ້​ການ​ບໍ​ລິ​ການ IMS ເພື່ອ​ໂທ​ໂດຍ​ບໍ່​ມີ​ການ​ຊ່ວຍ​ເຫຼືອ​ຂອງ​ທ່ານ."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ອ່ານສະຖານະ ແລະຂໍ້ມູນລະບຸໂຕຕົນຂອງໂທລະສັບ"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ອະນຸຍາດໃຫ້ແອັບຯ ເຂົ້າເຖິງຄວາມສາມາດການໂທລະສັບຂອງອຸປະກອນ. ການກຳນົດສິດນີ້ເຮັດໃຫ້ແອັບຯສາມາດກວດສອບເບີໂທລະສັບ ແລະ ID ຂອງອຸປະກອນ, ບໍ່ວ່າການໂທຈະຍັງດຳເນີນຢູ່ ແລະເບີປາຍທາງເຊື່ອມຕໍ່ຢູ່ຫຼືບໍ່ກໍຕາມ."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ອ່ານສະຖານະໂທລະສັບພື້ນຖານ ແລະ ຕົວຕົນ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ອະນຸຍາດໃຫ້ແອັບເຂົ້າເຖິງຄຸນສົມບັດໂທລະສັບພື້ນຖານຂອງອຸປະກອນໄດ້."</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">"ເຫັນ ແລະ ຄວບຄຸມການໂທຜ່ານລະບົບ."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ແຕະເພື່ອລຶບຮູບແບບໃບໜ້າຂອງທ່ານ, ຈາກນັ້ນເພີ່ມໃບໜ້າຂອງທ່ານໃສ່ໃໝ່"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ຕັ້ງຄ່າການປົດລັອກດ້ວຍໜ້າ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ປົດລັອກໂທລະສັບຂອງທ່ານໂດຍການເບິ່ງມັນ"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ເພື່ອໃຊ້ການປົດລັອກດ້ວຍໜ້າ, ກະລຸນາເປີດໃຊ້ "<b>"ສິດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບ"</b>" ໃນການຕັ້ງຄ່າ &gt; ຄວາມເປັນສ່ວນຕົວ"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"ຕັ້ງຄ່າວິທີເພີ່ມເຕີມເພື່ອປົດລັອກ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ແຕະເພື່ອເພີ່ມລາຍນິ້ວມື"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ປົດລັອກດ້ວຍລາຍນິ້ວມື"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"ກຳ​ລັງ​ກຽມ <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ກຳລັງເປີດແອັບຯ."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ກຳລັງສຳເລັດການເປີດລະບົບ."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ປິດໜ້າຈໍໄວ້ບໍ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ໃນລະຫວ່າງການຕັ້ງຄ່າລາຍນິ້ວມືຂອງທ່ານ, ທ່ານກົດປຸ່ມເປີດປິດ.\n\nໂດຍປົກກະຕິນີ້ຈະເປັນການປິດໜ້າຈໍຂອງທ່ານ."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ປິດໄວ້"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ຍົກເລີກ"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ສືບຕໍ່ການຕັ້ງຄ່າບໍ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ທ່ານກົດປຸ່ມເປີດປິດ, ປົກກະຕິນີ້ຈະປິດໜ້າຈໍ.\n\nລອງແຕະຄ່ອຍໆໃນລະຫວ່າງຕັ້ງຄ່າລາຍນິ້ວມືຂອງທ່ານ."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ປິດໜ້າຈໍ"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ສືບຕໍ່ການຕັ້ງຄ່າ"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ສືບຕໍ່"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ກຳລັງເຮັດວຽກ"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tap to return to game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Choose game"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 180e102..70f0f42 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Programai leidžiama naudoti IMS paslaugą, kad būtų galima atlikti skambučius be jūsų įsikišimo."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"skaityti telefono būseną ir tapatybę"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Leidžiama programai pasiekti telefono funkcijas įrenginyje. Šis leidimas suteikia teisę programai nustatyti telefono numerį ir įrenginio ID, tai, ar skambutis aktyvus, ir skambučiu prijungtą nuotolinį numerį."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"skaityti pagrindinę telefonijos būsenos ir tapatybės informaciją"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Programai leidžiama pasiekti pagrindines įrenginio telefonijos funkcijas."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"nukreipti skambučius per sistemą"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Programai leidžiama nukreipti jos skambučius per sistemą siekiant pagerinti skambinimo paslaugas."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"matyti ir valdyti skambučius per sistemą."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Palieskite, kad ištrintumėte veido modelį, tada iš naujo pridėkite veidą"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Atrakinimo pagal veidą nustatymas"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Atrakinkite telefoną pažiūrėję į jį"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Jei norite naudoti atrakinimą pagal veidą, įjunkite parinktį "<b>"Prieiga prie fotoaparato"</b>" skiltyje „Nustatymai“ &gt; „Privatumas“"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Daugiau atrakinimo metodų nustatymas"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Palieskite, kad pridėtumėte kontrolinį kodą"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Atrakinimas kontroliniu kodu"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Ruošiama „<xliff:g id="APPNAME">%1$s</xliff:g>“."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Paleidžiamos programos."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Užbaigiamas paleidimas."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Išjungti ekraną?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Nustatydami kontrolinį kodą paspaudėte maitinimo mygtuką.\n\nĮprastai juo išjungiamas ekranas."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Išjungti"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Atšaukti"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Tęsti sąranką?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Paspaudėte maitinimo mygtuką, taip paprastai išjungiamas ekranas.\n\nNustatydami kontrolinį kodą, pabandykite jį švelniai paliesti."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Išjungti ekraną"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Tęsti sąranką"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Tęsti kontrolinio kodo patvirtinimą?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Paspaudėte maitinimo mygtuką, taip paprastai išjungiamas ekranas.\n\nNorėdami patvirtinti kontrolinį kodą, pabandykite jį švelniai paliesti."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Išjungti ekraną"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Tęsti"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Vykdoma „<xliff:g id="APP">%1$s</xliff:g>“"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Palieskite, kad grįžtumėte į žaidimą"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Pasirinkite žaidimą"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 8f3e989..1df7774 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -221,7 +221,7 @@
     <string name="turn_on_radio" msgid="2961717788170634233">"Ieslēgt bezvadu tīklu"</string>
     <string name="turn_off_radio" msgid="7222573978109933360">"Izslēgt bezvadu tīklu"</string>
     <string name="screen_lock" msgid="2072642720826409809">"Bloķēt ekrānu"</string>
-    <string name="power_off" msgid="4111692782492232778">"Strāvas padeve ir izslēgta."</string>
+    <string name="power_off" msgid="4111692782492232778">"Izslēgt tālruni"</string>
     <string name="silent_mode_silent" msgid="5079789070221150912">"Zvanītājs izslēgts"</string>
     <string name="silent_mode_vibrate" msgid="8821830448369552678">"Zvanītājs vibrācijas režīmā"</string>
     <string name="silent_mode_ring" msgid="6039011004781526678">"Zvanītājs ieslēgts"</string>
@@ -245,7 +245,7 @@
     <string name="global_actions" product="tv" msgid="3871763739487450369">"Android TV opcijas"</string>
     <string name="global_actions" product="default" msgid="6410072189971495460">"Tālruņa opcijas"</string>
     <string name="global_action_lock" msgid="6949357274257655383">"Ekrāna bloķētājs"</string>
-    <string name="global_action_power_off" msgid="4404936470711393203">"Strāvas padeve izslēgta."</string>
+    <string name="global_action_power_off" msgid="4404936470711393203">"Izslēgt strāvas padevi"</string>
     <string name="global_action_power_options" msgid="1185286119330160073">"Barošana"</string>
     <string name="global_action_restart" msgid="4678451019561687074">"Restartēt"</string>
     <string name="global_action_emergency" msgid="1387617624177105088">"Ārkārtas situācija"</string>
@@ -474,6 +474,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Ļauj lietotnei izmantot tūlītējās ziņojumapmaiņas pakalpojumu, lai veiktu zvanus bez jūsu ziņas."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lasīt tālruņa statusu un identitāti"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Ļauj lietotnei piekļūt ierīces tālruņa funkcijām. Ar šo atļauju lietotne var noteikt tālruņa numuru un ierīču ID, zvana statusu un attālo numuru, ar ko ir izveidots savienojums, veicot zvanu."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lasīt telefonijas statusa un identitātes pamatinformāciju"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Lietotnei tiek atļauta piekļuve telefonijas pamatfunkcijām ierīcē."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"maršrutēt zvanus sistēmā"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Ļauj lietotnei maršrutēt tās zvanus sistēmā, lai uzlabotu zvanīšanas pieredzi."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"skatīt un kontrolēt zvanus sistēmā."</string>
@@ -625,6 +627,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Pieskarieties, lai izdzēstu sejas modeli, un pēc tam vēlreiz pievienojiet seju"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Autorizācijas pēc sejas iestatīšana"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Atbloķējiet tālruni, skatoties uz to"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Lai izmantotu autorizāciju pēc sejas, sadaļā Iestatījumi &gt; Konfidencialitāte ieslēdziet opciju "<b>"Piekļuve kamerai"</b>"."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Citi atbloķēšanas veidi"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Pieskarieties, lai pievienotu pirksta nospiedumu"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Autorizācija ar pirksta nospiedumu"</string>
@@ -1296,10 +1299,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Notiek lietotnes <xliff:g id="APPNAME">%1$s</xliff:g> sagatavošana."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Notiek lietotņu palaišana."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Tiek pabeigta sāknēšana."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vai izslēgt ekrānu?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Iestatot pirksta nospiedumu, jūs nospiedāt barošanas pogu.\n\nTādējādi parasti tiek izslēgts ekrāns."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Izslēgt"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Atcelt"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Vai turpināt iestatīšanu?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Jūs nospiedāt barošanas pogu — tādējādi parasti tiek izslēgts ekrāns.\n\nMēģiniet viegli pieskarties, iestatot pirksta nospiedumu."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Izslēgt ekrānu"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Turpināt iestatīšanu"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Vai apstiprināt pirksta nospiedumu?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Jūs nospiedāt barošanas pogu — tādējādi parasti tiek izslēgts ekrāns.\n\nMēģiniet viegli pieskarties, lai apstiprinātu pirksta nospiedumu."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Izslēgt ekrānu"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Turpināt"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> darbojas"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Pieskarieties, lai atgrieztos spēlē"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Spēles izvēlēšanās"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 9717112..cc86005 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Дозволува апликацијата да ја користи услугата IMS за повици без ваша интервенција."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"прочитај ги статусот и идентитетот  на телефонот"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Овозможува апликацијата да пристапи кон карактеристиките на телефонот на уредот. Оваа дозвола овозможува апликацијата да ги утврди телефонскиот број и ID на уредот, дали повикот е активен и далечинскиот број поврзан со повикот."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"читање основен телефонски статус и идентитет"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Дозволете апликацијава да пристапи до основните телефонски функции на уредот."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"пренасочи повици преку системот"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Дозволете ѝ на апликацијата да ги пренасочи повиците преку системот за да го подобри искуството при јавувањето."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"гледање и контролирање повици преку системот."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Допрете за да го избришете вашиот модел на лице, а потоа повторно додајте го лицето"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Поставете „Отклучување со лик“"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Отклучете го телефонот со гледање во него"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"За да користите „Отклучување со лик“, вклучете "<b>"Пристап до камерата"</b>" во „Поставки &gt; Приватност“"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Поставете уште начини за отклучување"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Допрете за да додадете отпечаток"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Отклучување со отпечаток на прст"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Се подготвува <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Се стартуваат апликациите."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Подигањето завршува."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Да се исклучи екранот?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Додека го поставувавте вашиот отпечаток, го притиснавте копчето за вклучување.\n\nОва обично го исклучува екранот."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Исклучи"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Откажи"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Дали да продолжи поставувањето?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Го притиснавте копчето за вклучување — така обично се исклучува екранот.\n\nДопрете лесно додека го поставувате отпечатокот."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Исклучи го екранот"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Продолжи"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Продолжи"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> работи"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Допрете за да се вратите во играта"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Избор на игра"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 12eb2a2..2517704 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"നിങ്ങളുടെ ഇടപെടൽ ഇല്ലാതെ കോളുകൾ ചെയ്യാൻ IMS സേവനം ഉപയോഗിക്കുന്നതിന് ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ഫോൺ നിലയും ഐഡന്റിറ്റിയും റീഡുചെയ്യുക"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ഉപകരണത്തിന്റെ ഫോൺ സവിശേഷതകൾ ആക്‌സസ്സുചെയ്യാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു. ഈ അനുമതി ഫോൺ നമ്പർ, ഉപകരണ ഐഡികൾ, ഒരു കോൾ സജീവമാണോയെന്നത്, ഒരു കോൾ കണക്റ്റുചെയ്‌ത വിദൂര നമ്പർ എന്നിവ നിർണ്ണയിക്കാൻ അപ്ലിക്കേഷനെ അനുവദിക്കുന്നു."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ടെലിഫോണിന്റെ അടിസ്ഥാന നിലയും ഐഡന്റിറ്റിയും വായിക്കാൻ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ഉപകരണത്തിലെ ടെലിഫോൺ സംബന്ധമായ അടിസ്ഥാന ഫീച്ചറുകൾ ആക്‌സസ് ചെയ്യാൻ ആപ്പിനെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"കോളുകൾ സിസ്റ്റത്തിലൂടെ വിടുക"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"കോളിംഗ് അനുഭവം ‌മെച്ചപ്പെടുത്തുന്നതിനായി തങ്ങളുടെ ‌കോളുകൾ സിസ്റ്റത്തിലേയ്ക്ക് വഴിതിരിച്ചുവിടാൻ ആപ്പുകളെ അനുവദിക്കുന്നു."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"സിസ്‌റ്റത്തിലൂടെ കോളുകൾ കാണുകയും നിയന്ത്രിക്കുകയും ചെയ്യുക."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"നിങ്ങളുടെ മുഖ മോഡൽ ഇല്ലാതാക്കാൻ ടാപ്പ് ചെയ്യുക, തുടർന്ന് അത് വീണ്ടും ചേർക്കുക"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ഫെയ്‌സ് അൺലോക്ക് സജ്ജീകരിക്കുക"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ഫോണിലേക്ക് നോക്കി അത് അൺലോക്ക് ചെയ്യുക"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ഫെയ്‌സ് അൺലോക്ക് ഉപയോഗിക്കാൻ, ക്രമീകരണം &gt; സ്വകാര്യത എന്നതിൽ "<b>"ക്യാമറാ ആക്‌സസ്"</b>" ഓണാക്കുക"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"അൺലോക്ക് ചെയ്യുന്നതിനുള്ള കൂടുതൽ വഴികൾ സജ്ജീകരിക്കുക"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ഫിംഗർപ്രിന്റ് ചേർക്കാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ഫിംഗർപ്രിന്റ് അൺലോക്ക്"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> തയ്യാറാക്കുന്നു."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"അപ്ലിക്കേഷനുകൾ ആരംഭിക്കുന്നു."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ബൂട്ട് ചെയ്യൽ പൂർത്തിയാകുന്നു."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"സ്‌ക്രീൻ ഓഫാക്കണോ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ഫിംഗർപ്രിന്റ് സജ്ജീകരിച്ച് കൊണ്ടിരുന്നപ്പോൾ നിങ്ങൾ പവർ ബട്ടൺ അമർത്തി.\n\nഇത് സാധാരണയായി സ്ക്രീൻ ഓഫാകുന്നതിന് കാരണമാകും."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ഓഫാക്കുക"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"റദ്ദാക്കുക"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"സജ്ജീകരണം തുടരണോ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"നിങ്ങൾ പവർ ബട്ടൺ അമർത്തി — സാധാരണയായി ഇത് സ്ക്രീൻ ഓഫാകുന്നതിന് കാരണമാകും.\n\nനിങ്ങളുടെ ഫിംഗർപ്രിന്റ് സജ്ജീകരിക്കുമ്പോൾ മൃദുവായി ടാപ്പ് ചെയ്ത് നോക്കുക."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"സ്ക്രീൻ ഓഫാക്കുക"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"സജ്ജീകരണം തുടരുക"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"തുടരുക"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> പ്രവർത്തിക്കുന്നു"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ഗെയിമിലേക്ക് മടങ്ങാൻ ടാപ്പ് ചെയ്യുക"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ഗെയിം തിരഞ്ഞെടുക്കുക"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 68fdb07..6be8243 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Апп нь дуудлага хийхдээ таны оролцоогүйгээр IMS үйлчилгээг ашиглах боломжтой."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"утасны статус ба таниулбарыг унших"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Апп нь төхөөрөмжийн утасны функцд хандах боломжтой. Энэ зөвшөөрөл нь апп-д утасны дугаар болон төхөөрөмжийн ID-г, дуудлага идэвхтэй эсэх, холын дугаар дуудлагаар холбогдсон байгаа эсэхийг тогтоох боломжийг олгоно,"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"утасны үндсэн статус болон таних мэдээллийг уншуулна уу"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Аппад төхөөрөмжийн утасны үндсэн онцлогт хандах боломжийг олгоно."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"системээр дамжуулах дуудлага"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Дуудлагыг сайжруулахын тулд дуудлагаа системээр дамжуулах зөвшөөрлийг апп-д олгодог."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"систем дэх дуудлагыг харах болон хянах."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Нүүрний загвараа устгахын тулд товшоод, дараа нь царайгаа дахин нэмнэ үү"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Царайгаар түгжээ тайлахыг тохируулах"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Утас руугаа харж түгжээг нь тайлна уу"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Царайгаар түгжээ тайлахыг ашиглахын тулд Тохиргоо &gt; Нууцлал хэсэгт "<b>" Камерын хандалтыг "</b>" асаана уу"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Түгжээ тайлах илүү олон арга тохируулна уу"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Хурууны хээ нэмэхийн тулд товшино уу"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Хурууны хээгээр түгжээ тайлах"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Бэлдэж байна <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Апп-г эхлүүлж байна."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Эхлэлийг дуусгаж байна."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Дэлгэцийг унтраах уу?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Та хурууны хээгээ тохируулж байх үед Асаах/унтраах товчийг дарсан байна.\n\nЭнэ нь ихэвчлэн таны дэлгэцийг унтраана."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Унтраах"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Цуцлах"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Тохируулгыг үргэлжлүүлэх үү?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Та асаах/унтраах товчийг дарсан байна — энэ нь ихэвчлэн дэлгэцийг унтраадаг.\n\nХурууны хээгээ тохируулж байх үедээ зөөлөн товшиж үзнэ үү."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Дэлгэцийг унтраах"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Тохируулгыг үргэлжлүүлэх"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Үргэлжлүүлэх"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ажиллаж байна"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Тоглоом руу буцахын тулд товших"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Тоглоом сонгох"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index bf9143d..69279ee 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"आपल्‍या हस्तक्षेपाशिवाय अ‍ॅपला कॉल करण्‍यासाठी IMS सेवा वापरण्याची अनुमती देते."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"फोन स्थिती आणि ओळख वाचा"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"डिव्हाइस च्या फोन वैशिष्ट्यांवर अ‍ॅक्सेस करण्यास ॲपला अनुमती देते. ही परवानगी कॉल ॲक्टिव्हेट असला किंवा नसला तरीही, फोन नंबर आणि डिव्हाइस आयडी आणि कॉलद्वारे कनेक्ट केलेला रिमोट नंबर निर्धारित करण्यासाठी ॲपला अनुमती देते."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"टेलिफोनी सुविधांशी संबंधित मूलभूत स्टेटस आणि ओळखीशी संबंधित माहिती वाचा"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"यामुळे ॲपला डिव्हाइसची मूलभूत टेलिफोनी वैशिष्ट्ये अ‍ॅक्सेस करण्याची अनुमती मिळते."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"प्रणालीच्या माध्यमातून कॉल रूट करा"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"कॉल करण्याचा अनुभव सुधारण्यासाठी ॲपला त्याचे कॉल प्रणालीच्या माध्यमातून रूट करू देते."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"सिस्टम वापरून कॉल पहा आणि नियंत्रण ठेवा."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"फेस मॉडेल हटवण्यासाठी टॅप करा, त्यानंतर तुमचा चेहरा पुन्हा जोडा"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"फेस अनलॉक सेट करा"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"तुमच्या फोनकडे पाहून तो अनलॉक करा"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"फेस अनलॉक वापरण्यासाठी, सेटिंग्ज &gt; गोपनीयता येथे "<b>"कॅमेरा अ‍ॅक्सेस"</b>" सुरू करा"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"अनलॉक करण्याच्या आणखी पद्धती सेट करा"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"फिंगरप्रिंट जोडण्यासाठी टॅप करा"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"फिंगरप्रिंट अनलॉक"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> तयार करत आहे."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"अ‍ॅप्स सुरू करत आहे."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"बूट समाप्त होत आहे."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"स्क्रीन बंद करायची आहे का?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"तुम्ही तुमची फिंगरप्रिंट सेट करत असताना पॉवर बटण दाबले.\n\nयामुळे सहसा तुमची स्क्रीन बंद होते."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"बंद करा"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"रद्द करा"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"सेट करणे पुढे सुरू ठेवायचे का?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"तुम्ही पॉवर बटण दाबले — हे सहसा स्क्रीन बंद करते.\n\nतुमचे फिंगरप्रिंट सेट करताना हलके टॅप करून पहा."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"स्क्रीन बंद करा"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"सेट करणे सुरू ठेवा"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"पुढे सुरू ठेवा"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"रन होणारे <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"गेमवर परत जाण्यासाठी टॅप करा"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"गेम निवडा"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 05395c9..27aac1a 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Membenarkan apl menggunakan perkhidmatan IMS untuk membuat panggilan tanpa campur tangan anda."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"baca status dan identiti telefon"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Membenarkan apl mengakses ciri telefon pada peranti. Kebenaran ini membolehkan apl menentukan nombor telefon dan ID peranti, sama ada panggilan aktif dan nombor jauh yang dihubungkan dengan panggilan."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"membaca status telefoni asas dan identiti"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Membenarkan apl mengakses ciri telefoni asas peranti."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"halakan panggilan menerusi sistem"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Membenarkan apl menghalakan panggilan menerusi sistem untuk meningkatkan pengalaman panggilan."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"lihat dan kawal panggilan melalui sistem."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Ketik untuk memadamkan model wajah anda, kemudian tambahkan wajah anda semula"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Sediakan Buka Kunci Wajah"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Buka kunci telefon anda dengan melihat telefon anda"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Untuk menggunakan Buka Kunci Wajah, hidupkan "<b>"akses Kamera"</b>" dalam Tetapan &gt; Privasi"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Sediakan lebih banyak cara untuk membuka kunci"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Ketik untuk menambahkan cap jari"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Buka Kunci Cap Jari"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Menyediakan <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Memulakan apl."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"But akhir."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Matikan skrin?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Semasa menyediakan cap jari anda, anda menekan butang Kuasa.\n\nTindakan ini biasanya mematikan skrin anda."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Matikan"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Batal"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Teruskan persediaan?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Anda menekan butang kuasa — tindakan ini biasanya mematikan skrin.\n\nCuba ketik dengan perlahan semasa menetapkan cap jari anda."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Matikan skrin"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Teruskan persediaan"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Terus mengesahkan cap jari anda?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Anda menekan butang kuasa — tindakan ini biasanya mematikan skrin.\n\nCuba ketik dengan perlahan untuk mengesahkan cap jari anda."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Matikan skrin"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Teruskan"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> dijalankan"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Ketik untuk kembali ke permainan"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Pilih permainan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 4f97ef9..eefbe15 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"သင့်ရဲ့ဝင်ရောက်စွက်ဖက်မှုမပါဘဲ IMS ဝန်ဆောင်မှုကိုအသုံးပြုပြီး ဖုန်းခေါ်ဆိုနိုင်ရန် အပ်ဖ်ကို ခွင့်ပြုထားပါ။"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ဖုန်းရဲ့ အခြေအနေ နှင့် အမှတ်သညာအား ဖတ်ခြင်း"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"အပလီကေးရှင်းအား ဖုန်းရဲ့ စွမ်းဆောင်ချက်များအား သုံးခွင့်ပြုပါ။ အပလီကေးရှင်းအနေဖြင့် ဖုန်းနံပါတ်၊ စက်နံပါတ်၊ ဖုန်းခေါ်နေမှု ရှိမရှိနှင့် တဖက်မှ ဖုန်းနံပါတ် များအား သိရှိနိုင်ပါသည်"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"အခြေခံတယ်လီဖုန်းအခြေအနေနှင့် အထောက်အထားကို ဖတ်ခြင်း"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"စက်၏ အခြေခံတယ်လီဖုန်းဝန်ဆောင်မှုများသုံးရန် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ခေါ်ဆိုမှုများကို စနစ်မှတစ်ဆင့် ဖြတ်သန်းခွင့်ပြုပါ"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"ခေါ်ဆိုမှု အတွေ့အကြုံ ပိုမိုကောင်းမွန်လာစေရန်အတွက် အက်ပ်၏ ခေါ်ဆိုမှုအား စနစ်မှတစ်ဆင့် ဖြတ်သန်းရန် ခွင့်ပြုပါသည်။"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"စနစ်မှတစ်ဆင့် ခေါ်ဆိုမှုများကို ကြည့်ရှုထိန်းချုပ်ပါ။"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"သင်၏မျက်နှာနမူနာကို ဖျက်ရန် တို့ပါ။ ထို့နောက် သင့်မျက်နှာကို ထပ်ထည့်ပါ"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်းကို ထည့်သွင်းပါ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"သင့်ဖုန်းကိုကြည့်၍ သော့ဖွင့်ပါ"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်းကို သုံးရန် "<b>"ကင်မရာ သုံးခွင့်"</b>" ကို ‘ဆက်တင်များ &gt; ကန့်သတ်ဆက်တင်’ တွင်ဖွင့်ပါ"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"သော့ဖွင့်ရန် နောက်ထပ်နည်းလမ်းများကို စနစ်ထည့်သွင်းပါ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"လက်ဗွေထည့်ရန် တို့ပါ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"လက်ဗွေသုံး လော့ခ်ဖွင့်ခြင်း"</string>
@@ -1172,8 +1175,8 @@
     <string name="delete" msgid="1514113991712129054">"ဖျက်ရန်"</string>
     <string name="copyUrl" msgid="6229645005987260230">"URLအား ကူးခြင်း"</string>
     <string name="selectTextMode" msgid="3225108910999318778">"စာသား ရွေးရန်"</string>
-    <string name="undo" msgid="3175318090002654673">"တစ်ဆင့်နောက်ပြန်ရန်"</string>
-    <string name="redo" msgid="7231448494008532233">"ထပ်လုပ်ပါ"</string>
+    <string name="undo" msgid="3175318090002654673">"နောက်ပြန်ရန်"</string>
+    <string name="redo" msgid="7231448494008532233">"ပြန်လုပ်ရန်"</string>
     <string name="autofill" msgid="511224882647795296">"အော်တိုဖြည့်"</string>
     <string name="textSelectionCABTitle" msgid="5151441579532476940">"စာတိုရွေးချယ်မှု"</string>
     <string name="addToDictionary" msgid="8041821113480950096">"အဘိဓာန်ထဲ ထည့်ပါ"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> အားပြင်ဆင်နေသည်။"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"အက်ပ်များကို စတင်နေ"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"လုပ်ငန်းစနစ်ထည့်သွင်း၍ ပြန်လည်စတင်ရန် ပြီးပါပြီ"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ဖန်သားပြင်ကို ပိတ်မလား။"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"သင့်လက်ဗွေကို စနစ်ထည့်သွင်းနေစဉ် ဖွင့်ပိတ်ခလုတ်ကို ဖိထားပါ။\n\n၎င်းက အများအားဖြင့် သင့်ဖန်သားပြင်ကို ပိတ်ပါသည်။"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ပိတ်ရန်"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"မလုပ်တော့"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ဆက်လက်၍ စနစ်ထည့်သွင်းလိုပါသလား။"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ဖွင့်ပိတ်ခလုတ်ကို သင်နှိပ်ခဲ့သည် — ၎င်းက ပုံမှန်အားဖြင့် စခရင်ကို ပိတ်စေသည်။\n\nသင့်လက်ဗွေကို ထည့်သွင်းသောအခါ ဖွဖွတို့ကြည့်ပါ။"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"စခရင် ပိတ်ရန်"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"စနစ်ဆက်ထည့်သွင်းရန်"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ရှေ့ဆက်ရန်"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> က အလုပ်လုပ်နေသည်"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ဂိမ်းသို့ ပြန်သွားရန် တို့ပါ"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ဂိမ်းကို ရွေးခြင်း"</string>
@@ -1549,7 +1556,7 @@
     <string name="sync_too_many_deletes" msgid="6999440774578705300">"ပယ်ဖျက်မည့်ကန့်သတ်နှုန်းကျော်လွန်သည်"</string>
     <string name="sync_too_many_deletes_desc" msgid="7409327940303504440">"<xliff:g id="TYPE_OF_SYNC">%2$s</xliff:g>၊  account <xliff:g id="ACCOUNT_NAME">%3$s</xliff:g> အတွက် စုစုပေါင်း <xliff:g id="NUMBER_OF_DELETED_ITEMS">%1$d</xliff:g> အရာဖျက်ထားပါသည်။ သင်ဘာလုပ်ချင်ပါလဲ?"</string>
     <string name="sync_really_delete" msgid="5657871730315579051">"ဤအရာများကိုဖျက်ပါ"</string>
-    <string name="sync_undo_deletes" msgid="5786033331266418896">"ဖျက်ပီးသည်များကို ပယ်ဖျက်ရန်"</string>
+    <string name="sync_undo_deletes" msgid="5786033331266418896">"အားလုံးကို မဖျက်တော့ရန်"</string>
     <string name="sync_do_nothing" msgid="4528734662446469646">"လက်ရှိ ဘာမှမလုပ်ရန်"</string>
     <string name="choose_account_label" msgid="5557833752759831548">"အကောင့် တစ်ခု ရွေးပါ"</string>
     <string name="add_account_label" msgid="4067610644298737417">"အကောင့်တစ်ခုထည့်ရန်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index ccf5e97d..4d8d2b0 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Lar appen bruke nettprattjenesten til å ringe uten at du gjør noe."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lese telefonstatus og -identitet"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Lar appen bruke enhetens telefonfunksjoner. Med denne tillatelsen kan appen finne telefonnummer og enhets-ID-er, registrere om en samtale pågår, og se det eksterne nummeret det opprettes en forbindelse med via oppringing."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lese grunnleggende telefonstatus og identitet"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Gir appen tilgang til de grunnleggende telefonfunksjonene på enheten."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"send anrop gjennom systemet"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Lar appen sende anrop gjennom systemet for å forbedre anropsopplevelsen."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"se og kontrollere anrop i systemet."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Trykk for å slette ansiktsmodellen din, og legg deretter til ansiktet på nytt"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Konfigurer ansiktslås"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Lås opp telefonen ved å se på den"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"For å bruke ansiktslås, slå på "<b>"Kameratilgang"</b>" i Innstillinger &gt; Personvern"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfigurer flere måter å låse opp på"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Trykk for å legge til et fingeravtrykk"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Opplåsing med fingeravtrykk"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Forbereder <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Starter apper."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Ferdigstiller oppstart."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vil du slå av skjermen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Da du konfigurerte fingeravtrykket, trykket du på av/på-knappen.\n\nDette slår vanligvis av skjermen."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Slå av"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Avbryt"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Vil du fortsette konfigureringen?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Du har trykket på av/på-knappen – dette slår vanligvis av skjermen.\n\nPrøv å trykke lett mens du konfigurerer fingeravtrykket ditt."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Slå av skjermen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Fortsett konfig."</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Fortsett bekreftelse av fingeravtrykket?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Du har trykket på av/på-knappen – dette slår vanligvis av skjermen.\n\nPrøv å trykke lett for å bekrefte fingeravtrykket ditt."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Slå av skjermen"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Fortsett"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Trykk for å gå tilbake til spillet"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Velg et spill"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 99e3375..12c4464 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"तपाईँको हस्तक्षेप बिना नै कल गर्न IMS सेवा प्रयोग गर्न एपलाई अनुमति दिन्छ।"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"फोन स्थिति र पहिचान पढ्नुहोस्"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"उपकरणको फोन विशेषताहरूको पहुँच गर्न एपलाई अनुमति दिन्छ। यस अनुमतिले फोन नम्बर र उपकरणको IDs, कल सक्षम छ कि छैन र कलद्वारा जोडिएको टाढाको नम्बर निर्धारण गर्न अनुमति दिन्छ।"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"टेलिफोन सेवाका आधारभूत स्थिति र त्यसको पहिचानसम्बन्धी जानकारी रिड गर्ने"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"तपाईंले यो अनुमति दिनुभयो भने यो एपले यस डिभाइसको टेलिफोन सेवाका आधारभूत सुविधाहरू प्रयोग गर्न सक्छ।"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"प्रणाली मार्फत कल गर्न दिनुहोस्‌"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"कल गर्दाको अनुभवलाई सुधार्न यस एपलाई प्रणाली मार्फत कलहरू गर्न अनुमति दिन्छ।"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"प्रणालीमार्फत कलहरू हेर्नुका साथै तिनीहरूलाई नियन्त्रण गर्नुहोस्‌।"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"आफ्नो फेस मोडेल मेटाउन ट्याप गर्नुहोस् अनि आफ्नो अनुहार फेरि दर्ता गर्नुहोस्"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"फेस अनलक सेटअप गर्नुहोस्"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"फोनमा हेरेकै भरमा फोन अनलक गर्नुहोस्"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"फेस अनलक प्रयोग गर्न \"सेटिङ तथा गोपनीयता\" मा गई "<b>"क्यामेरा प्रयोग गर्ने अनुमति"</b>" दिनुहोस्"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"अनलक गर्ने अन्य तरिकाहरू सेटअप गर्नुहोस्"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"फिंगरप्रिन्ट हाल्न ट्याप गर्नुहोस्"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"फिंगरप्रिन्ट अनलक"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> तयारी गर्दै।"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"सुरुवात एपहरू।"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"बुट पुरा हुँदै।"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"स्क्रिन अफ गर्ने हो?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"आफ्नो फिंगरप्रिन्ट सेटअप गर्दा तपाईंले पावर बटन थिच्नुभयो।\n\nयसो गर्दा सामान्यतया तपाईंको स्क्रिन अफ हुन्छ।"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"अफ गर्नुहोस्"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"रद्द गर्नुहोस्"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"सेटअप गर्ने प्रक्रिया जारी राख्ने हो?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"तपाईंले पावर बटन थिच्नुभयो — सामान्यतया स्क्रिन अफ गर्न यो बटन थिच्ने गरिन्छ।\n\nफिंगरप्रिन्ट सेटअप भइन्जेल हल्का तरिकाले यो बटन ट्याप गर्नुहोस्।"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"स्क्रिन अफ गर्नुहोस्"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"सेटअप जारी राख्नुस्"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"जारी राख्नुहोस्"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> चलिरहेको छ"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"खेलमा फर्कन ट्याप गर्नुहोस्"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"खेल छनौट गर्नुहोस्‌"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index a1b45bf..858ca00 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Hiermee kan de app de IMS-service gebruiken om te bellen zonder je tussenkomst."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"telefoonstatus en -identiteit lezen"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Hiermee kan de app toegang krijgen tot de telefoonfuncties van het apparaat, Met deze rechten kan de app het telefoonnummer en de apparaat-ID\'s bepalen, of een gesprek actief is, en het andere telefoonnummer waarmee wordt gebeld."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"basistelefoonstatus en -identiteit lezen"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Hiermee geef je de app toegang tot de basistelefoonfuncties van het apparaat."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"gesprekken doorschakelen via het systeem"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Hiermee kan de app de bijbehorende gesprekken doorschakelen via het systeem om de belfunctionaliteit te verbeteren."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"gesprekken via het systeem bekijken en beheren"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tik om je gezichtsmodel te verwijderen en voeg je gezicht opnieuw toe"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Ontgrendelen via gezichtsherkenning instellen"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Ontgrendel je telefoon door ernaar te kijken"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Als je Ontgrendelen via gezichtsherkenning wilt gebruiken, zet je "<b>"Cameratoegang"</b>" aan via Instellingen &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Stel meer manieren in om te ontgrendelen"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tik om een vingerafdruk toe te voegen"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Ontgrendelen met vingerafdruk"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> voorbereiden."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Apps starten."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Opstarten afronden."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Scherm uitzetten?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Toen je je vingerafdruk instelde, heb je op de aan/uit-knop gedrukt.\n\nDaarmee wordt het scherm meestal uitgezet."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Uitzetten"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Annuleren"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Doorgaan met instellen?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Je hebt op de aan/uit-knop gedrukt. Zo zet je meestal het scherm uit.\n\nRaak de knop voorzichtig aan terwijl je je vingerafdruk instelt."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Scherm uitzetten"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Doorgaan met instellen"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Doorgaan met verificatie van je vingerafdruk?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Je hebt op de aan/uit-knop gedrukt. Zo zet je meestal het scherm uit.\n\nRaak de knop voorzichtig aan om je vingerafdruk te verifiëren."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Scherm uitzetten"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Doorgaan"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> wordt uitgevoerd"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Tik om terug te keren naar de game"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Game kiezen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index afe0441..52015c2 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ଆପଣଙ୍କ ହସ୍ତକ୍ଷେପ ବିନା କଲ୍‍ କରିପାରିବା ପାଇଁ ଆପ୍‌କୁ IMS ସେବା ବ୍ୟବହାର କରିବାକୁ ଦିଏ।"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ଫୋନ୍‍ ସ୍ଥିତି ଓ ପରିଚୟ ପଢ଼ନ୍ତୁ"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ଆପ୍‌କୁ ଡିଭାଇସ୍‌ର ଫୋନ୍‌ ବୈଶିଷ୍ଟ୍ୟ ଆକ୍ସେସ୍‍ କରିବାକୁ ଅନୁମତି ଦେଇଥାଏ। ଏହି ଅନୁମତି ଆପ୍‌କୁ ଫୋନ୍‌ ନମ୍ବର୍ ଓ ଡିଭାଇସ୍‌ IDଗୁଡ଼ିକୁ ନିର୍ଦ୍ଧାରଣ କରିବାକୁ ଅନୁମତି ଦେଇଥାଏ, କଲ୍ ସକ୍ରିୟ ଥିଲେ ବି ଏବଂ କଲ୍ ଦ୍ୱାରା ସଂଯୋଗ ଥିବା ରିମୋଟ୍‌ ନମ୍ବର୍‌କୁ ମଧ୍ୟ।"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ବେସିକ ଟେଲିଫୋନି ସ୍ଥିତି ଏବଂ ପରିଚୟ ପଢ଼ନ୍ତୁ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ଡିଭାଇସର ବେସିକ ଟେଲିଫୋନି ଫିଚରଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହା ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ସିଷ୍ଟମ୍‍ ଜରିଆରେ କଲ୍‌ର ମାର୍ଗ ବଦଳାଇପାରେ"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"କଲ୍‍ କରିବାର ଅନୁଭୂତି ବଢ଼ାଇବାକୁ ସିଷ୍ଟମ୍‍ ଜରିଆରେ ଆପର କଲ୍‍ଗୁଡ଼ିକୁ ରୁଟ୍‍ କରିବାକୁ ଏହାକୁ ଅନୁମତି ଦେଇଥାଏ।"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ସିଷ୍ଟମ୍‍ ମାଧ୍ୟମରେ କଲ୍‍ଗୁଡ଼ିକୁ ଦେଖିଥାଏ ଏବଂ ନିୟନ୍ତ୍ରଣ କରିଥାଏ।"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ଆପଣଙ୍କ ଫେସ୍ ମଡେଲକୁ ଡିଲିଟ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ, ତା\'ପରେ ପୁଣି ଆପଣଙ୍କ ଫେସ୍ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ଫେସ୍ ଅନଲକ୍ ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ଫୋନକୁ ଦେଖି ଏହାକୁ ଅନଲକ୍ କରନ୍ତୁ"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ଫେସ ଅନଲକ ବ୍ୟବହାର କରିବା ପାଇଁ, ସେଟିଂସ ଏବଂ ଗୋପନୀୟତାରେ "<b>"କ୍ୟାମେରା ଆକ୍ସେସ"</b>"କୁ ଚାଲୁ କରନ୍ତୁ"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"ଅନଲକ୍ କରିବା ପାଇଁ ଆହୁରି ଅଧିକ ଉପାୟ ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ଏକ ଟିପଚିହ୍ନ ଯୋଗ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ଫିଙ୍ଗରପ୍ରିଣ୍ଟ ଅନଲକ୍"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> ପ୍ରସ୍ତୁତ କରାଯାଉଛି।"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ଆପ୍‍ ଆରମ୍ଭ କରାଯାଉଛି।"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ବୁଟ୍‍ ସମାପ୍ତ କରୁଛି।"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ସ୍କ୍ରିନ୍ ବନ୍ଦ କରିବେ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ଆପଣଙ୍କ ଟିପଚିହ୍ନ ସେଟ୍ ଅପ୍ କରିବା ସମୟରେ, ଆପଣ ପାୱାର ବଟନ୍ ଦବାଇଛନ୍ତି।\n\nଏହା ସାଧାରଣତଃ ଆପଣଙ୍କ ସ୍କ୍ରିନକୁ ବନ୍ଦ କରେ।"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ବନ୍ଦ କରନ୍ତୁ"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ସେଟଅପ କରିବା ଜାରି ରଖିବେ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ଆପଣ ପାୱାର ବଟନ ଦବାଇଛନ୍ତି — ଏହା ସାଧାରଣତଃ ଆପଣଙ୍କ ସ୍କ୍ରିନକୁ ବନ୍ଦ କରିଥାଏ।\n\nଆପଣଙ୍କ ଟିପଚିହ୍ନ ସେଟ ଅପ କରିବା ସମୟରେ ଧୀରେ ଟାପ କରିବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ସ୍କ୍ରିନ ବନ୍ଦ କରନ୍ତୁ"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ସେଟଅପ କରିବା ଜାରି ରଖ"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ଜାରି ରଖନ୍ତୁ"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ଚାଲୁଛି"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ଗେମ୍‌କୁ ଫେରିଆସିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ଗେମ୍ ଚୟନ କରନ୍ତୁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 1877ebe..6826a2d 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ਐਪ ਨੂੰ ਤੁਹਾਡੇ ਦਖ਼ਲ ਤੋਂ ਬਿਨਾਂ ਕਾਲਾਂ ਕਰਨ ਲਈ IMS ਸੇਵਾ ਵਰਤਣ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ।"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ਫ਼ੋਨ ਸਥਿਤੀ ਅਤੇ ਪਛਾਣ ਪੜ੍ਹੋ"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"ਐਪ ਨੂੰ ਡੀਵਾਈਸ ਦੀਆਂ ਫ਼ੋਨ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਤੱਕ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ। ਇਹ ਇਜਾਜ਼ਤ ਐਪ ਨੂੰ ਫ਼ੋਨ ਨੰਬਰ ਅਤੇ ਡੀਵਾਈਸ ਆਈ.ਡੀ. ਨਿਰਧਾਰਤ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦੀ ਹੈ, ਇੱਕ ਕਾਲ ਕਿਰਿਆਸ਼ੀਲ ਹੈ ਜਾਂ ਨਹੀਂ ਅਤੇ ਰਿਮੋਟ ਨੰਬਰ ਇੱਕ ਕਾਲ ਨਾਲ ਕਨੈਕਟ ਹੈ ਜਾਂ ਨਹੀਂ।"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ਮੂਲ ਟੈਲੀਫ਼ੋਨੀ ਸਥਿਤੀ ਅਤੇ ਪਛਾਣ ਨੂੰ ਪੜ੍ਹੋ"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"ਐਪ ਨੂੰ ਡੀਵਾਈਸ \'ਤੇ ਮੂਲ ਟੈਲੀਫ਼ੋਨੀ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦਿੰਦੀ ਹੈ।"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"ਸਿਸਟਮ ਰਾਹੀਂ ਕਾਲਾਂ ਰੂਟ ਕਰੋ"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"ਕਾਲ ਕਰਨ ਦੇ ਅਨੁਭਵ ਨੂੰ ਬਿਹਤਰ ਬਣਾਉਣ ਲਈ ਐਪ ਨੂੰ ਇਸਦੀਆਂ ਕਾਲਾਂ ਨੂੰ ਸਿਸਟਮ ਰਾਹੀਂ ਰੂਟ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦਿੰਦੀ ਹੈ।"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ਸਿਸਟਮ ਰਾਹੀਂ ਕਾਲਾਂ ਦੇਖੋ ਅਤੇ ਕੰਟਰੋਲ ਕਰੋ।"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ਆਪਣੇ ਚਿਹਰੇ ਦਾ ਮਾਡਲ ਮਿਟਾਉਣ ਲਈ ਟੈਪ ਕਰੋ, ਫਿਰ ਆਪਣਾ ਚਿਹਰਾ ਦੁਬਾਰਾ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ਫ਼ੇਸ ਅਣਲਾਕ ਦਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ਆਪਣੇ ਫ਼ੋਨ ਵੱਲ ਦੇਖ ਕੇ ਇਸਨੂੰ ਅਣਲਾਕ ਕਰੋ"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ਫ਼ੇਸ ਅਣਲਾਕ ਨੂੰ ਵਰਤਣ ਲਈ, ਸੈਟਿੰਗਾਂ &gt; ਪਰਦੇਦਾਰੀ ਵਿੱਚ ਜਾ ਕੇ "<b>"ਕੈਮਰਾ ਪਹੁੰਚ"</b>" ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"ਅਣਲਾਕ ਕਰਨ ਦੇ ਹੋਰ ਤਰੀਕਿਆਂ ਦਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਅਣਲਾਕ"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> ਤਿਆਰ ਕਰ ਰਿਹਾ ਹੈ।"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ਐਪਸ ਚਾਲੂ ਕਰ ਰਿਹਾ ਹੈ।"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ਬੂਟ ਪੂਰਾ ਕਰ ਰਿਹਾ ਹੈ।"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ਕੀ ਸਕ੍ਰੀਨ ਬੰਦ ਕਰਨੀ ਹੈ?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਦਾ ਸੈੱਟਅੱਪ ਕਰਨ ਵੇਲੇ, ਤੁਸੀਂ ਪਾਵਰ ਬਟਨ ਨੂੰ ਦਬਾਇਆ ਹੈ।\n\nਇਹ ਆਮ ਤੌਰ \'ਤੇ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਨੂੰ ਬੰਦ ਕਰ ਦਿੰਦਾ ਹੈ।"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ਬੰਦ ਕਰੋ"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ਰੱਦ ਕਰੋ"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ਕੀ ਸੈੱਟਅੱਪ ਜਾਰੀ ਰੱਖਣਾ ਹੈ?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ਤੁਸੀਂ ਪਾਵਰ ਬਟਨ ਨੂੰ ਦਬਾਇਆ ਹੈ — ਇਹ ਆਮ ਤੌਰ \'ਤੇ ਸਕ੍ਰੀਨ ਨੂੰ ਬੰਦ ਕਰ ਦਿੰਦਾ ਹੈ।\n\nਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਦਾ ਸੈੱਟਅੱਪ ਕਰਦੇ ਸਮੇਂ ਹਲਕਾ ਜਿਹਾ ਟੈਪ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ਸਕ੍ਰੀਨ ਬੰਦ ਕਰੋ"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ਸੈੱਟਅੱਪ ਜਾਰੀ ਰੱਖੋ"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ਜਾਰੀ ਰੱਖੋ"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ਚੱਲ ਰਿਹਾ ਹੈ"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ਗੇਮ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ਗੇਮ ਚੁਣੋ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 6f2198e..e16d2af 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Zezwala aplikacji na korzystanie z usługi komunikatora, by nawiązywać połączenia bez Twojego udziału."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"odczytywanie stanu i informacji o telefonie"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Pozwala aplikacji na dostęp do funkcji telefonicznych urządzenia. Aplikacja z tym uprawnieniem może odczytać numer telefonu i identyfikator urządzenia, sprawdzić, czy połączenie jest aktywne, oraz poznać numer, z którym jest nawiązane połączenie."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"odczyt podstawowych informacji na temat stanu usług telefonicznych i tożsamości"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Zezwól na dostęp aplikacji do podstawowych funkcji telefonicznych na urządzeniu."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"przekazywanie połączeń przez system"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Zezwala aplikacji na przekazywanie połączeń przez system, by poprawić ich jakość."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"przeglądanie i kontrolowanie połączeń w systemie."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Kliknij, aby usunąć model twarzy, a następnie ponownie dodaj skan twarzy"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Skonfiguruj rozpoznawanie twarzy"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Popatrz na ekran telefonu, aby go odblokować"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Aby używać rozpoznawania twarzy, włącz "<b>"dostęp do aparatu"</b>" w Ustawieniach i prywatności"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Skonfiguruj więcej sposobów odblokowywania"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Kliknij, aby dodać odcisk palca"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Odblokowywanie odciskiem palca"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Przygotowuję aplikację <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Uruchamianie aplikacji."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Kończenie uruchamiania."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Wyłączyć ekran?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Podczas konfigurowania odcisku palca naciśnięto przycisk zasilania.\n\nZwykle powoduje to wyłączenie ekranu."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Wyłącz"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Anuluj"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Kontynuować konfigurowanie?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Naciśnięto przycisk zasilania — zwykle powoduje to wyłączenie ekranu.\n\nKlikaj delikatnie podczas konfigurowania odcisku palca."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Wyłącz ekran"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Konfiguruj dalej"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Kontynuować weryfikację odcisku palca?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Naciśnięto przycisk zasilania — zwykle powoduje to wyłączenie ekranu.\n\nKliknij delikatnie, aby zweryfikować odcisk palca."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Wyłącz ekran"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Dalej"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Działa <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Kliknij, by wrócić do gry"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Wybierz grę"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 8e7cc3d..52d7992 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite que o app use o serviço de mensagens instantâneas para fazer chamadas sem sua intervenção."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ler status e identidade do telefone"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite que o app acesse os recursos de telefonia do dispositivo. Esta permissão autoriza o app a determinar o número de telefone e IDs de dispositivo, quando uma chamada está ativa, e o número remoto conectado a uma chamada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ler status e identidade básicos de telefonia"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite que o app acesse os recursos básicos de telefonia do dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"encaminhar chamadas pelo sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite que o app encaminhe suas chamadas por meio do sistema para melhorar a experiência com chamadas."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ver e controlar chamadas pelo sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurar o Desbloqueio facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Desbloqueie o smartphone olhando para ele"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para usar o Desbloqueio facial, ative o "<b>"acesso à câmera"</b>" em Configurações &gt; Privacidade"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configure mais formas de desbloquear a tela"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Toque para adicionar uma impressão digital"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueio por impressão digital"</string>
@@ -1208,7 +1211,7 @@
     <string name="whichOpenLinksWith" msgid="1120936181362907258">"Abrir links com"</string>
     <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Abrir links com <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
     <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Abrir links do domínio <xliff:g id="HOST">%1$s</xliff:g> usando <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
-    <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Conceder acesso"</string>
+    <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Permitir acesso"</string>
     <string name="whichEditApplication" msgid="6191568491456092812">"Editar com"</string>
     <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Editar com %1$s"</string>
     <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Editar"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Preparando <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Iniciando apps."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Concluindo a inicialização."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Desligar a tela?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Durante a configuração da sua impressão digital, você pressionou o botão liga/desliga.\n\nNormalmente, essa ação desliga a tela."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Desligar"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancelar"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continuar a configuração?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Você pressionou o botão liga/desliga. Normalmente, essa ação desliga a tela.\n\nToque levemente na tela durante a configuração da impressão digital."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Desligar a tela"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continuar configuração"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuar a verificação da digital?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Você pressionou o botão liga/desliga. Normalmente, essa ação desliga a tela.\n\nToque levemente na tela para verificar sua impressão digital."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Desligar a tela"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuar"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Toque para voltar ao jogo"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Escolha o jogo"</string>
@@ -2110,12 +2117,12 @@
     <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Esta notificação foi rebaixada a Silenciosa. Toque para enviar seu feedback."</string>
     <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação foi classificada com maior prioridade. Toque para enviar seu feedback."</string>
     <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação foi classificada com menor prioridade. Toque para enviar seu feedback."</string>
-    <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações aprimoradas"</string>
-    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações aprimoradas. As Notificações adaptáveis do Android não estão mais disponíveis."</string>
+    <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações avançadas"</string>
+    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações avançadas. As Notificações adaptáveis do Android não estão mais disponíveis."</string>
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
     <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Desativar"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Saiba mais"</string>
-    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações aprimoradas substituíram as notificações adaptáveis. Esse recurso exibe ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações aprimoradas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender chamadas telefônicas e controlar o Não perturbe."</string>
+    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações avançadas substituíram as notificações adaptáveis. Esse recurso exibe ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações avançadas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender chamadas telefônicas e controlar o Não perturbe."</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string>
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"A bateria pode acabar antes da recarga normal"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"A Economia de bateria foi ativada para aumentar a duração da carga"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index bf8ff8e..2ea9f39 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite que a app utilize o serviço IMS para fazer chamadas sem a sua intervenção."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ler o estado e a identidade do telemóvel"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite que a app aceda às funcionalidades de telefone do dispositivo. Esta autorização permite que a app determine o número de telefone e IDs do dispositivo, se alguma chamada está ativa e qual o número remoto ligado por uma chamada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ler o estado e a identidade de telefonia básicos"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite à app aceder às funcionalidades básicas de telefonia do dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"encaminhar chamadas através do sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite que a app encaminhe as respetivas chamadas através do sistema de modo a melhorar a experiência da chamada."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ver e controlar chamadas através do sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para eliminar o seu modelo de rosto e, em seguida, adicione o seu rosto novamente"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configure o Desbloqueio facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Desbloqueie o telemóvel ao olhar para ele"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para utilizar o Desbloqueio facial, ative o "<b>"Acesso à câmara"</b>" em Definições &gt; Privacidade"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configure mais formas de desbloquear"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Toque para adicionar uma impressão digital"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueio por impressão digital"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"A preparar o <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"A iniciar aplicações"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"A concluir o arranque."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Pretende desligar o ecrã?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Quando configurou a sua impressão digital, premiu o botão ligar/desligar.\n\nGeralmente, esta ação desativa o seu ecrã."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Desligar"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancelar"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continuar a configuração?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Premiu o botão ligar/desligar. Geralmente, esta ação desliga o ecrã.\n\nExperimente tocar levemente ao configurar a sua impressão digital."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Desligar ecrã"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continuar configur."</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuar a validar a impressão digital?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Premiu o botão ligar/desligar. Geralmente, esta ação desliga o ecrã.\n\nExperimente tocar levemente para validar a sua impressão digital."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Desligar ecrã"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuar"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Toque para regressar ao jogo."</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Selecionar jogo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 8e7cc3d..52d7992 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite que o app use o serviço de mensagens instantâneas para fazer chamadas sem sua intervenção."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ler status e identidade do telefone"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite que o app acesse os recursos de telefonia do dispositivo. Esta permissão autoriza o app a determinar o número de telefone e IDs de dispositivo, quando uma chamada está ativa, e o número remoto conectado a uma chamada."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ler status e identidade básicos de telefonia"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite que o app acesse os recursos básicos de telefonia do dispositivo."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"encaminhar chamadas pelo sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permite que o app encaminhe suas chamadas por meio do sistema para melhorar a experiência com chamadas."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ver e controlar chamadas pelo sistema."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Toque para excluir seu modelo de rosto e crie um novo"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurar o Desbloqueio facial"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Desbloqueie o smartphone olhando para ele"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para usar o Desbloqueio facial, ative o "<b>"acesso à câmera"</b>" em Configurações &gt; Privacidade"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configure mais formas de desbloquear a tela"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Toque para adicionar uma impressão digital"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Desbloqueio por impressão digital"</string>
@@ -1208,7 +1211,7 @@
     <string name="whichOpenLinksWith" msgid="1120936181362907258">"Abrir links com"</string>
     <string name="whichOpenLinksWithApp" msgid="6917864367861910086">"Abrir links com <xliff:g id="APPLICATION">%1$s</xliff:g>"</string>
     <string name="whichOpenHostLinksWithApp" msgid="2401668560768463004">"Abrir links do domínio <xliff:g id="HOST">%1$s</xliff:g> usando <xliff:g id="APPLICATION">%2$s</xliff:g>"</string>
-    <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Conceder acesso"</string>
+    <string name="whichGiveAccessToApplicationLabel" msgid="7805857277166106236">"Permitir acesso"</string>
     <string name="whichEditApplication" msgid="6191568491456092812">"Editar com"</string>
     <string name="whichEditApplicationNamed" msgid="8096494987978521514">"Editar com %1$s"</string>
     <string name="whichEditApplicationLabel" msgid="1463288652070140285">"Editar"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Preparando <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Iniciando apps."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Concluindo a inicialização."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Desligar a tela?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Durante a configuração da sua impressão digital, você pressionou o botão liga/desliga.\n\nNormalmente, essa ação desliga a tela."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Desligar"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Cancelar"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continuar a configuração?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Você pressionou o botão liga/desliga. Normalmente, essa ação desliga a tela.\n\nToque levemente na tela durante a configuração da impressão digital."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Desligar a tela"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continuar configuração"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuar a verificação da digital?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Você pressionou o botão liga/desliga. Normalmente, essa ação desliga a tela.\n\nToque levemente na tela para verificar sua impressão digital."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Desligar a tela"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuar"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Toque para voltar ao jogo"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Escolha o jogo"</string>
@@ -2110,12 +2117,12 @@
     <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"Esta notificação foi rebaixada a Silenciosa. Toque para enviar seu feedback."</string>
     <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação foi classificada com maior prioridade. Toque para enviar seu feedback."</string>
     <string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação foi classificada com menor prioridade. Toque para enviar seu feedback."</string>
-    <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações aprimoradas"</string>
-    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações aprimoradas. As Notificações adaptáveis do Android não estão mais disponíveis."</string>
+    <string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações avançadas"</string>
+    <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações avançadas. As Notificações adaptáveis do Android não estão mais disponíveis."</string>
     <string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
     <string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Desativar"</string>
     <string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Saiba mais"</string>
-    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações aprimoradas substituíram as notificações adaptáveis. Esse recurso exibe ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações aprimoradas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender chamadas telefônicas e controlar o Não perturbe."</string>
+    <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações avançadas substituíram as notificações adaptáveis. Esse recurso exibe ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações avançadas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender chamadas telefônicas e controlar o Não perturbe."</string>
     <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string>
     <string name="dynamic_mode_notification_title" msgid="9205715501274608016">"A bateria pode acabar antes da recarga normal"</string>
     <string name="dynamic_mode_notification_summary" msgid="4141614604437372157">"A Economia de bateria foi ativada para aumentar a duração da carga"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 8f05f9f..ac5eca7 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -474,6 +474,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Permite aplicației să folosească serviciul IMS pentru apeluri, fără intervenția dvs."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"citește starea și identitatea telefonului"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Permite aplicației să acceseze funcțiile de telefon ale dispozitivului. Cu această permisiune aplicația stabilește numărul de telefon și ID-urile de dispozitiv, dacă un apel este activ, precum și numărul de la distanță conectat printr-un apel."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"să citească informații de bază, precum activitatea și starea telefonului"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Permite ca aplicația să acceseze funcțiile de telefonie de bază ale dispozitivului."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"să direcționeze apelurile prin intermediul sistemului"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Permiteți aplicației să direcționeze apelurile prin intermediul sistemului pentru a îmbunătăți calitatea apelurilor."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Vedeți și controlați apelurile prin intermediul sistemului."</string>
@@ -625,6 +627,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Atingeți pentru a șterge modelul facial, apoi adăugați din nou fața"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Configurați Deblocarea facială"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Deblocați-vă telefonul uitându-vă la acesta"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Pentru a folosi Deblocarea facială, activați "<b>"Accesul la cameră"</b>" în Setări și confidențialitate"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Configurați mai multe moduri de deblocare"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Atingeți ca să adăugați o amprentă"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Deblocare cu amprenta"</string>
@@ -1296,10 +1299,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Se pregătește <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Se pornesc aplicațiile."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Se finalizează pornirea."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Dezactivați ecranul?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Ați apăsat butonul de pornire în timpul configurării amprentei.\n\nDe obicei, această acțiune dezactivează ecranul."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Dezactivați"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Anulați"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Continuați configurarea?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Ați apăsat butonul de pornire. De obicei, această acțiune dezactivează ecranul.\n\nAtingeți ușor când vă configurați amprenta."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Dezactivați ecranul"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Continuați configurarea"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Continuați cu verificarea amprentei?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Ați apăsat butonul de pornire. De obicei, această acțiune dezactivează ecranul.\n\nAtingeți ușor pentru verificarea amprentei."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Dezactivați ecranul"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Continuați"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Rulează <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Atingeți pentru a reveni la joc"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Alegeți jocul"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 927ec93..6182832 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Позволяет приложению совершать звонки с помощью службы IMS без вашего вмешательства."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"Получение данных о статусе телефона"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Приложение получит доступ к функциям телефона на устройстве. Кроме того, оно сможет определять номера телефонов и серийные номера моделей, состояние активности вызова, а также удаленные номера, с которыми установлено соединение."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"Считывание статуса и идентификационной информации телефонии"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Приложение получит доступ к основным функциям телефонии на устройстве."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"перенаправлять звонки в системе"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Приложение сможет перенаправлять звонки в системе с целью улучшения качества связи."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Управление вызовами через систему"</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Нажмите, чтобы удалить модель лица, а затем добавьте ее снова."</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Настройка фейсконтроля"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Вы сможете разблокировать телефон, просто посмотрев на него."</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Чтобы использовать фейсконтроль, разрешите "<b>"доступ к камере"</b>". Для этого перейдите в настройки и нажмите \"Конфиденциальность\"."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Настройте другие способы разблокировки"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Нажмите, чтобы добавить отпечаток пальца."</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Разблокировка по отпечатку пальца"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Подготовка приложения \"<xliff:g id="APPNAME">%1$s</xliff:g>\"..."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Запуск приложений."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Окончание загрузки..."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Отключить экран?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Во время настройки отпечатка пальца вы нажали кнопку питания.\n\nОбычно это действие приводит к отключению экрана."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Отключить"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Отмена"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Продолжить настройку?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Вы нажали кнопку питания. Обычно это приводит к отключению экрана.\n\nПри добавлении отпечатка пальца слегка прикоснитесь к кнопке."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Отключить экран"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Продолжить настройку"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Продолжить"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Нажмите, чтобы вернуться в игру."</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Выберите игру"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index c63708f..c5e76c8 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ඔබේ මැදිහත්වීමකින් තොරව ඇමතුම් සිදු කිරීමට  IMS සේවාව භාවිතයට යෙදුමට ඉඩ දෙන්න."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"දුරකථනයේ තත්වය සහ අනන්‍යතාවය කියවීම"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"උපාංගයේ දුරකථන විශේෂාංග වෙත ප්‍රවේශයට යෙදුමට ඉඩ දෙයි.  ඇමතුම සක්‍රිය වුවත්, සහ ඇමතුමකින් දුරස්ථ අංකය සම්බන්ධ වුවත් දුරකථන අංකය සහ උපාංග ID හඳුනා ගැනීමට මෙම අවසරය යෙදුමට ඉඩ දෙයි."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"මූලික දුරකථන තත්ත්වය සහ අනන්‍යතාව කියවන්න"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"උපාංගයේ මූලික දුරකථන විශේෂාංග වෙත ප්‍රවේශ වීමට යෙදුමට ඉඩ දෙයි."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"පද්ධතිය හරහා ඇමතුම් මාර්ගගත කරන්න"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"ඇමතුම් අත්දැකීම වැඩිදියුණු කිරීම සඳහා යෙදුමට පද්ධතිය හරහා එහි ඇමතුම් මාර්ගගත කිරීමට ඉඩ දෙයි."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"පද්ධතිය හරහා ඇමතුම් බලන්න සහ පාලනය කරන්න."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ඔබගේ මුහුණත ආකෘතිය මැකීමට තට්ටු කරන්න, අනතුරුව ඔබගේ මුහුණ නැවත එක් කරන්න"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"මුහුණෙන් අගුළු හැරීම පිහිටුවන්න"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ඔබගේ දුරකථනය දෙස බැලීමෙන් එහි අගුලු හරින්න"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"මුහුණෙන් අගුලු හැරීම භාවිත කිරීමට, සැකසීම් &gt; පෞද්ගලිකත්වය තුළ "<b>"කැමරා ප්‍රවේශය"</b>" ක්‍රියාත්මක කරන්න"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"අගුලු හැරීමට තවත් ක්‍රම සකසන්න"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"ඇඟිලි සලකුණක් එක් කිරීමට තට්ටු කරන්න"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ඇඟිලි සලකුණු අගුළු හැරීම"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> සූදානම් කරමින්."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"යෙදුම් ආරම්භ කරමින්."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"ඇරඹුම අවසාන කරමින්."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"තිරය ක්‍රියාවිරහිත කරන්නද?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ඔබගේ ඇඟිලි සලකුණ පිහිටුවන අතරතුර ඔබ බල බොත්තම එබුවේය.\n\nමෙය සාමාන්‍යයෙන් ඔබගේ තිරය ක්‍රියාවිරහිත කරයි."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ක්‍රියාවිරහිත කරන්න"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"අවලංගු කරන්න"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"පිහිටුවීම දිගටම කරන්නද?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"ඔබ බල බොත්තම එබුවේය — සාමාන්‍යයෙන් මෙය තිරය ක්‍රියාවිරහිත කරයි.\n\nඔබගේ ඇඟිලි සලකුණ පිහිටුවන අතරතුර සැහැල්ලුවෙන් තට්ටු කිරීමට උත්සාහ කරන්න."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"තිරය අක්‍රිය කරන්න"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"පිහිටුවීම දිගටම කර."</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ඉදිරියට යන්න"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ධාවනය වෙමින්"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"ක්‍රීඩාව වෙත ආපසු යාමට තට්ටු කරන්න"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"ක්‍රීඩාව තෝරන්න"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6645176..dfd34e7 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Umožňuje aplikácii používať službu okamžitých správ (IMS) na volanie bez intervencie používateľa."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"čítať stav a identitu telefónu"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Umožňuje aplikácii pristupovať k telefónnym funkciám zariadenia. Aplikácia s týmto povolením môže určiť telefónne číslo a ID zariadenia, či práve prebieha hovor, a vzdialené číslo, s ktorým je prostredníctvom hovoru nadviazané spojenie."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"čítanie základného stavu a identity súvisiacich s telefonovaním"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Umožňuje aplikácii prístup k základným telefonickým funkciám zariadenia."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"presmerovanie hovorov cez systém"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Umožňuje aplikácii presmerovať hovory cez systém na účely zlepšenia kvality hovorov."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"zobrazenie a kontrola hovorov prostredníctvom systému."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Klepnutím odstráňte model tváre a potom znova pridajte svoju tvár"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Nastavte odomknutie tvárou"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Odomykajte telefón tak, že sa naň pozriete"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Ak chcete používať odomknutie tvárou, v sekcii Nastavenia &gt; Ochrana súkromia zapnite "<b>"prístup ku kamere"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Nastavte viac spôsobov odomknutia"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Klepnutím pridajte odtlačok prsta"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Odomknutie odtlačkom prsta"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Pripravuje sa aplikácia <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Prebieha spúšťanie aplikácií."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Prebieha dokončovanie spúšťania."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Chcete vypnúť obrazovku?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Pri nastavovaní odtlačku prsta ste stlačili vypínač.\n\nObrazovka sa tým zvyčajne vypne."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Vypnúť"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Zrušiť"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Chcete pokračovať v nastavovaní?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Stlačili ste vypínač. Obvykle tým vypnete obrazovku.\n\nPri nastavovaní odtlačku prsta skúste klepnúť jemne."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Vypnúť obrazovku"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Pokračovať v nastav."</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Pokračovať v overovaní odtlačku prsta?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Stlačili ste vypínač. Obvykle tým vypnete obrazovku.\n\nAk chcete overiť odtlačok prsta, skúste klepnúť jemne."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Vypnúť obrazovku"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Pokračovať"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Spustená aplikácia: <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Klepnutím prejdete späť do hry"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Vyberte hru"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 935b1e8..931fc5b 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Aplikaciji dovoljuje uporabo storitev IMS za opravljanje klicev brez vašega posredovanja."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"branje stanja in identitete telefona"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Aplikaciji omogoča dostop do funkcij telefona v napravi. S tem dovoljenjem lahko aplikacija določi telefonsko številko in ID-je naprave, določi lahko tudi, ali je klic aktiven, in oddaljeno številko, s katero je klic povezan."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"branje stanja osnovne telefonije in identitete"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Aplikaciji omogoča dostop do osnovnih funkcij telefonije v napravi."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"Usmeri klice prek sistema"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Dovoli, da aplikacija usmeri klice prek sistema, da se tako izboljša izkušnja klicanja."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ogled in nadzor klicev prek sistema."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Dotaknite se, da izbrišete model obraza, in nato znova dodajte obraz."</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Nastavitev odklepanja z obrazom"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Odklenite telefon tako, da ga pogledate."</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Če želite uporabljati odklepanje z obrazom, v meniju »Nastavitve« &gt; »Zasebnost« vklopite možnost "<b>"Dostop do fotoaparata"</b>"."</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Nastavite več načinov odklepanja"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Dotaknite se, da dodate prstni odtis."</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Odklepanje s prstnim odtisom"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Pripravljanje aplikacije <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <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_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Želite izklopiti zaslon?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Pri nastavljanju prstnega odtisa ste pritisnili gumb za vklop.\n\nS tem običajno izklopite zaslon."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Izklopi"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Prekliči"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Ali želite nadaljevati nastavitev?"</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_positive_button" msgid="2095415838459356833">"Izklopi zaslon"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Nadaljuj nastavitev"</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>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Izklopi zaslon"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Nadaljuj"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> se izvaja"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Dotaknite se za vrnitev v igro"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Izberite igro"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 7bd3a53..937332b 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Lejon aplikacionin të përdorë shërbimin IMS për të kryer telefonata pa ndërhyrjen tënde."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"lexo statusin e telefonit dhe identitetin"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Lejon aplikacionin të hyjë në funksionet telefonike të pajisjes. Kjo leje i mundëson aplikacionit të përcaktojë numrin e telefonit dhe ID-të e pajisjes, nëse një telefonatë është aktive apo nëse numri në distancë është i lidhur me një telefonatë."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"lexo statusin dhe identitetin telefonik bazë"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Lejo që aplikacioni të ketë qasje në veçoritë telefonike bazë në pajisje."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"kalon telefonatat përmes sistemit"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Lejon që aplikacioni të kalojë telefonatat përmes sistemit për të përmirësuar përvojën e telefonatës."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Shiko dhe kontrollo telefonatat nëpërmjet sistemit."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Trokit për të fshirë modelin tënd të fytyrës, pastaj shtoje përsëri fytyrën tënde"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Konfiguro \"Shkyçjen me fytyrë\""</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Shkyçe telefonin duke parë tek ai"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Për të përdorur \"Shkyçjen me fytyrë\", aktivizo "<b>"Qasjen te kamera"</b>" te \"Cilësimet\" &gt; \"Privatësia\""</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfiguri më shumë mënyra për të shkyçur"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Trokit për të shtuar një gjurmë gishti"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Shkyçja me gjurmën e gishtit"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Po përgatit <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Aplikacionet e fillimit."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Po përfundon nisjen."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Të fiket ekrani?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Gjatë konfigurimit të gjurmës së gishtit tënd, ke shtypur butonin e \"Energjisë\".\n\nKjo zakonisht fik ekranin tënd."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Fik"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Anulo"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Të vazhdohet konfigurimi?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Shtype butonin e energjisë — zakonisht, kjo e fik ekranin.\n\nProvo të trokasësh lehtë ndërkohë që konfiguron gjurmën e gishtit."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Fik ekranin"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Vazhdo konfigurimin"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Të vazhdohet verifikimi i gjurmës?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Shtype butonin e energjisë — zakonisht, kjo e fik ekranin.\n\nProvo të trokasësh lehtë për të verifikuar gjurmën e gishtit."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Fik ekranin"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Vazhdo"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> është në punë"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Trokit për t\'u kthyer te loja"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Zgjidh një lojë"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 97b233c..72fd908 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -474,6 +474,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Дозвољава апликацији да користи услугу размене тренутних порука да би упућивала позиве без ваше интервенције."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"читање статуса и идентитета телефона"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Дозвољава апликацији да приступа функцијама телефона на уређају. Ова дозвола омогућава апликацији да утврди број телефона и ИД-ове уређаја, затим да ли је позив активан, као и број даљинског уређаја са којим је успостављен позив."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"очитавање основног телефонског статуса и идентитета"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Омогућава апликацији да приступа основним телефонским функцијама уређаја."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"преусмеравање позива преко система"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Дозвољава апликацији да преусмерава позиве преко система да би побољшала доживљај позивања."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"преглед и контрола позива преко система."</string>
@@ -625,6 +627,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Додирните да бисте избрисали модел лица, па поново додајте своје лице"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Подесите откључавање лицем"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Откључајте телефон тако што ћете га погледати"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Да бисте користили откључавање лицем, укључите "<b>"приступ камери"</b>" у одељку Подешавања &gt; Приватност"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Подесите још начина за откључавање"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Додирните да бисте додали отисак прста"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Откључавање отиском прста"</string>
@@ -1296,10 +1299,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Припрема се <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Покретање апликација."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Завршавање покретања."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Желите да искључите екран?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Притисли сте дугме за укључивање током подешавања отиска прста.\n\nТако се најчешће искључује екран."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Искључи"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Откажи"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Желите ли да наставите са подешавањем?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Притиснули сте дугме за укључивање – тиме обично искључујете екран.\n\nПробајте лагано да додирнете док подешавате отисак прста."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Искључи екран"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Настави подешавање"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Настави"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Апликација <xliff:g id="APP">%1$s</xliff:g> је покренута"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Додирните да бисте се вратили у игру"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Одаберите игру"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 740cfb9..e078da0 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Tillåter att appen använder tjänsten för snabbmeddelanden för att ringa samtal utan åtgärd från dig."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"läsa telefonens status och identitet"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Tillåter att appen kommer åt enhetens telefonfunktioner. Med den här behörigheten tillåts appen att identifiera mobilens telefonnummer och enhets-ID, om ett samtal pågår och vilket nummer samtalet är kopplat till."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"läsa grundläggande uppgifter om telefonistatus och identitet"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Ger appen åtkomst till grundläggande telefonifunktioner på enheten."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"dirigera samtal via systemet"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Appen tillåts att dirigera samtal via systemet för att förbättra samtalsupplevelsen."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"visa och styra samtal via systemet."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Tryck för att radera ansiktsmodellen och lägg sedan till ansiktet igen"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Konfigurera ansiktslås"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Lås upp telefonen genom att titta på den"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Om du vill använda ansiktslås aktiverar du "<b>"Kameraåtkomst"</b>" i Inställningar &gt; Integritet"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Konfigurera fler sätt att låsa upp"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Tryck för att lägga till ett fingeravtryck"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Fingeravtryckslås"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> förbereds."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Appar startas."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Uppgraderingen är klar."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vill du stänga av skärmen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Du tryckte på strömbrytaren när du skulle konfigurera fingeravtrycket.\n\nDet brukar leda till att skärmen stängs av."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Stäng av"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Avbryt"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Vill du fortsätta med konfigureringen?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Du tryckte på strömbrytaren, vilket vanligtvis stänger av skärmen.\n\nTesta att trycka lätt när du konfigurerar fingeravtrycket."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Stäng av skärmen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Fortsätt konfigurera"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Vill du verifiera ditt fingeravtryck?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Du tryckte på strömbrytaren, vilket vanligtvis stänger av skärmen.\n\nTesta att trycka lätt för att verifiera ditt fingeravtryck."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Stäng av skärmen"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Fortsätt"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> körs"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Återgå till spelet genom att trycka här"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Välj spel"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index fd1faa0..99fd107 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Huruhusu programu kutumia huduma ya IMS kupiga simu bila udhibiti wako."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"kusoma hali na kitambulisho cha simu"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Huruhusu programu kufikia vipengele vya simu vilivyo kwenye kifaa. Idhini hii inaruhusu programu kutambua nambari ya simu na kifaa, kama kuna simu inayopigwa, na nambari ya mbali iliyounganishwa kwenye simu."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"kusoma utambulisho na hali ya msingi ya simu"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Huruhusu programu kufikia vipengele vya msingi vya simu kwenye kifaa."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"elekeza simu kupitia mfumo"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Huruhusu programu kuelekeza simu zake kupitia mfumo ili kuboresha hali ya kupiga simu."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"kuona na kudhibiti simu kupitia mfumo."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Gusa ili ufute muundo wa uso wako, kisha uweke uso wako tena"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Weka mipangilio ya Kufungua kwa Uso"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Fungua simu yako kwa kuiangalia"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Ili utumie kipengele cha kufungua kwa uso, washa kipengele cha "<b>"ufikiaji wa Kamera"</b>" katika Mipangilio na Faragha"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Weka mipangilio ya mbinu zaidi za kufungua"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Gusa ili uweke alama ya kidole"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Kufungua kwa Alama ya Kidole"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Inaandaa <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Programu zinaanza"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Inamaliza kuwasha."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Ungependa kuzima skrini?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Ulipokuwa ukiweka alama ya kidole chako, ulibonyeza Kitufe cha kuwasha/kuzima.\n\nKwa kawaida, hatua hii huzima skrini yako."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Zima"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Ghairi"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Ungependa kuendelea kuweka mipangilio?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Umebonyeza kitufe cha kuwasha/kuzima — kwa kawaida, hali hii huzima skrini.\n\nJaribu kugusa taratibu unapoweka mipangilio ya alama ya kidole chako."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Zima skrini"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Endelea kuweka mipangilio"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Utaendelea kuthibitisha alama ya kidole chako?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Umebonyeza kitufe cha kuwasha/kuzima — kwa kawaida, hali hii huzima skrini.\n\nJaribu kugusa taratibu ili uthibitishe alama ya kidole chako."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Zima skrini"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Endelea"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> inaendelea"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Gusa ili urudi kwenye mchezo"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Chagua mchezo"</string>
diff --git a/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml
index 34b6a54..d686dd2 100644
--- a/core/res/res/values-sw600dp/config.xml
+++ b/core/res/res/values-sw600dp/config.xml
@@ -51,5 +51,7 @@
 
     <!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
     <bool name="config_showUserSwitcherByDefault">true</bool>
+
+    <integer name="config_chooser_max_targets_per_row">6</integer>
 </resources>
 
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 02ed848..e8f15fd 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -110,4 +110,7 @@
     <dimen name="immersive_mode_cling_width">380dp</dimen>
 
     <dimen name="floating_toolbar_preferred_width">544dp</dimen>
+
+    <dimen name="chooser_width">624dp</dimen>
+
 </resources>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 0690c31..0be25cd 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"உங்கள் குறுக்கீடின்றி IMS சேவையைப் பயன்படுத்தி அழைப்பதற்கு, ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"மொபைல் நிலை மற்றும் அடையாளத்தைப் படித்தல்"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"சாதனத்தின் மொபைல் அம்சங்களை அணுகப் ஆப்ஸை அனுமதிக்கிறது. மொபைல் மற்றும் சாதன ஐடிகள், அழைப்பு செயலில் உள்ளதா மற்றும் அழைப்பு மூலம் இணைக்கப்பட்ட தொலைக் கட்டுப்பாட்டு எண் ஆகியவற்றைத் தீர்மானிக்க இந்த அனுமதி ஆப்ஸை அனுமதிக்கிறது."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"அடிப்படையான டெலிஃபோனி நிலையையும் அடையாளத்தையும் படித்தல்"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"சாதனத்திலுள்ள அடிப்படையான டெலிஃபோனி அம்சங்களை அணுக ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"சிஸ்டம் மூலம் அழைப்புகளை ரூட் செய்தல்"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"அழைக்கும் அனுபவத்தை மேம்படுத்தும் பொருட்டு, சிஸ்டம் மூலம் தனது அழைப்புகளை ரூட் செய்ய ஆப்ஸை அனுமதிக்கும்."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"சிஸ்டம் மூலமாக அழைப்புகளைப் பார்த்தலும் கட்டுப்படுத்துதலும்."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"முகத் தோற்றப் பதிவைத் தட்டி நீக்கிவிட்டு உங்கள் முகத்தை மீண்டும் சேர்க்கவும்"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"\'முகம் காட்டித் திறத்தல்\' அம்சத்தை அமைத்தல்"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"மொபைலைப் பார்ப்பதன் மூலம் அதை அன்லாக் செய்யலாம்"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"முகம் காட்டித் திறத்தல் அம்சத்தைப் பயன்படுத்த, அமைப்புகள் &gt; தனியுரிமை என்பதற்குச் சென்று "<b>"கேமரா அணுகலை"</b>" இயக்கவும்"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"அன்லாக் செய்ய மேலும் பல வழிகளை அமையுங்கள்"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"கைரேகையைச் சேர்க்கத் தட்டுங்கள்"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"கைரேகை அன்லாக்"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g>ஐத் தயார்செய்கிறது."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ஆப்ஸ் தொடங்கப்படுகின்றன."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"துவக்குதலை முடிக்கிறது."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"திரையை ஆஃப் செய்யவா?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"கைரேகையை அமைக்கும்போது பவர் பட்டனை அழுத்திவிட்டீர்கள்.\n\nஇது திரையை ஆஃப் செய்துவிடும்."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ஆஃப் செய்"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ரத்துசெய்"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"அமைவைத் தொடரவா?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"நீங்கள் பவர் பட்டனை அழுத்தியுள்ளீர்கள் — வழக்கமாக இது திரையை ஆஃப் செய்யும்.\n\nஉங்கள் கைரேகையை அமைக்கும்போது மெதுவாகத் தொடுங்கள்."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"திரையை ஆஃப் செய்"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"அமைவைத் தொடர்க"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"தொடர்க"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> இயங்குகிறது"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"கேமிற்குச் செல்ல, தட்டவும்"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"கேமைத் தேர்வுசெய்க"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 313ce59..3aa7b04 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -422,7 +422,7 @@
     <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"మీ ఫోన్‌లో నిల్వ చేసి ఉన్న కాంటాక్ట్‌లకు సంబంధించిన డేటాను సవరించడానికి యాప్‌ను అనుమతిస్తుంది. ఈ అనుమతి, కాంటాక్ట్ డేటాను తొలగించడానికి యాప్‌లను అనుమతిస్తుంది."</string>
     <string name="permlab_readCallLog" msgid="1739990210293505948">"కాల్ లాగ్‌ను చదవడం"</string>
     <string name="permdesc_readCallLog" msgid="8964770895425873433">"ఈ యాప్‌ మీ కాల్ చరిత్రను చదవగలదు."</string>
-    <string name="permlab_writeCallLog" msgid="670292975137658895">"కాల్ లాగ్‌ను వ్రాయడం"</string>
+    <string name="permlab_writeCallLog" msgid="670292975137658895">"కాల్ లాగ్‌ను రాయడం"</string>
     <string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"ఇన్‌కమింగ్ మరియు అవుట్‌గోయింగ్ కాల్స్‌ల గురించిన డేటాతో సహా మీ టాబ్లెట్ యొక్క కాల్ లాగ్‌ను సవరించడానికి యాప్‌ను అనుమతిస్తుంది. హానికరమైన యాప్‌లు మీ కాల్ లాగ్‌ను ఎరేజ్ చేయడానికి లేదా సవరించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
     <string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"ఇన్‌కమింగ్ మరియు అవుట్‌గోయింగ్ కాల్స్‌కు సంబంధించిన డేటాతో సహా మీ Android TV పరికరం కాల్ లాగ్‌ను సవరించడానికి యాప్‌ని అనుమతిస్తుంది. హానికరమైన యాప్‌లు మీ కాల్ లాగ్‌ను తీసివేయడానికి లేదా సవరించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
     <string name="permdesc_writeCallLog" product="default" msgid="5903033505665134802">"ఇన్‌కమింగ్ మరియు అవుట్‌గోయింగ్ కాల్స్‌ల గురించిన డేటాతో సహా మీ ఫోన్ యొక్క కాల్ లాగ్‌ను సవరించడానికి యాప్‌ను అనుమతిస్తుంది. హానికరమైన యాప్‌లు మీ కాల్ లాగ్‌ను ఎరేజ్ చేయడానికి లేదా సవరించడానికి దీన్ని ఉపయోగించవచ్చు."</string>
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"మీ ప్రమేయం లేకుండా కాల్స్‌ చేయడం కోసం IMS సేవను ఉపయోగించడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"ఫోన్ స్టేటస్‌ మరియు గుర్తింపుని చదవడం"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"పరికరం యొక్క ఫోన్ ఫీచర్‌లను యాక్సెస్ చేయడానికి యాప్‌ను అనుమతిస్తుంది. ఈ అనుమతి ఫోన్ నంబర్ మరియు పరికరం IDలను, కాల్ సక్రియంగా ఉందా లేదా అనే విషయాన్ని మరియు కాల్ ద్వారా కనెక్ట్ చేయబడిన రిమోట్ నంబర్‌ను కనుగొనడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ప్రాథమిక టెలిఫోన్ స్టేటస్, గుర్తింపును చదవండి"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"పరికరం తాలూకు ప్రాథమిక టెలిఫోన్ ఫీచర్‌లను యాక్సెస్ చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"కాల్స్‌ను సిస్టమ్ ద్వారా వెళ్లేలా చేయి"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"కాలింగ్ అనుభవాన్ని మెరుగుపరచడం కోసం తన కాల్స్‌ను సిస్టమ్ ద్వారా వెళ్లేలా చేయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"సిస్టమ్ ద్వారా కాల్స్‌ను చూసి, నియంత్రించండి."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"ఫేస్ మోడల్‌ను తొలగించడానికి నొక్కండి, ఆపై మీ ముఖాన్ని మళ్లీ జోడించండి"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ఫేస్ అన్‌లాక్‌ను సెటప్ చేయండి"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"మీ ఫోన్‌ను చూడటం ద్వారా దాన్ని అన్‌లాక్ చేయండి"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"ఫేస్ అన్‌లాక్‌ను ఉపయోగించడానికి, సెట్టింగ్‌లు &gt; గోప్యతలో "<b>"కెమెరా యాక్సెస్"</b>"ను ఆన్ చేయండి"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"అన్‌లాక్ చేయడానికి మరిన్ని మార్గాలను సెటప్ చేయండి"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"వేలిముద్రను జోడించడానికి ట్యాప్ చేయండి"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"వేలిముద్ర అన్‌లాక్"</string>
@@ -725,7 +728,7 @@
     <string name="permlab_bindCarrierServices" msgid="2395596978626237474">"క్యారియర్ సేవలకు అనుబంధించడం"</string>
     <string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"క్యారియర్ సేవలకు అనుబంధించడానికి హోల్డర్‌ను అనుమతిస్తుంది. సాధారణ యాప్‌లకు ఎప్పటికీ అవసరం ఉండదు."</string>
     <string name="permlab_access_notification_policy" msgid="5524112842876975537">"అంతరాయం కలిగించవద్దును యాక్సెస్ చేయడం"</string>
-    <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"అంతరాయం కలిగించవద్దు ఎంపిక కాన్ఫిగరేషన్ చదవడానికి మరియు వ్రాయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
+    <string name="permdesc_access_notification_policy" msgid="8538374112403845013">"అంతరాయం కలిగించవద్దు ఎంపిక కాన్ఫిగరేషన్ చదవడానికి మరియు రాయడానికి యాప్‌ను అనుమతిస్తుంది."</string>
     <string name="permlab_startViewPermissionUsage" msgid="1504564328641112341">"వీక్షణ అనుమతి వినియోగాన్ని ప్రారంభించండి"</string>
     <string name="permdesc_startViewPermissionUsage" msgid="2820325605959586538">"యాప్‌నకు అనుమతి వినియోగాన్ని ప్రారంభించడానికి హోల్డర్‌‌ను అనుమతిస్తుంది. సాధారణ యాప్‌లకు ఎప్పటికీ ఇటువంటి అనుమతి అవసరం ఉండదు."</string>
     <string name="permlab_startViewAppFeatures" msgid="7955084203185903001">"యాప్ ఫీచర్‌లను చూడటాన్ని ప్రారంభించండి"</string>
@@ -1020,7 +1023,7 @@
     <string name="autofill_emirate" msgid="2544082046790551168">"ఎమిరేట్"</string>
     <string name="permlab_readHistoryBookmarks" msgid="9102293913842539697">"మీ వెబ్ బుక్‌మార్క్‌లు మరియు చరిత్రను చదవడం"</string>
     <string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"బ్రౌజర్ సందర్శించిన అన్ని URLల చరిత్ర గురించి మరియు అన్ని బ్రౌజర్ బుక్‌మార్క్‌ల గురించి చదవడానికి యాప్‌ను అనుమతిస్తుంది. గమనిక: ఈ అనుమతి మూడవ పక్షం బ్రౌజర్‌లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్‌ల ద్వారా అమలు చేయబడకపోవచ్చు."</string>
-    <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"వెబ్ బుక్‌మార్క్‌లు మరియు చరిత్రను వ్రాయడం"</string>
+    <string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"వెబ్ బుక్‌మార్క్‌లు మరియు చరిత్రను రాయడం"</string>
     <string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"మీ టాబ్లెట్‌లో నిల్వ చేయబడిన బ్రౌజర్ హిస్టరీని, బుక్‌మార్క్‌లను ఎడిట్ చేయడానికి యాప్‌ను అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను ఎరేజ్ చేయడానికి లేదా ఎడిట్ చేయడానికి యాప్‌ను అనుమతించవచ్చు. గమనిక: ఈ అనుమతిని థర్డ్ పార్టీ బ్రౌజర్‌లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్‌లు అమలు చేయకపోవచ్చు."</string>
     <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"మీ Android TV పరికరంలో నిల్వ చేసిన బ్రౌజర్ చరిత్ర లేదా బుక్‌మార్క్‌లను సవరించడానికి యాప్‌ని అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను తీసివేయడానికి లేదా సవరించడానికి యాప్‌ని అనుమతించవచ్చు. గమనిక: ఈ అనుమతి మూడవ-పక్ష బ్రౌజర్‌లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్‌ల ద్వారా అమలు కాకపోవచ్చు."</string>
     <string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"మీ ఫోన్‌లో నిల్వ చేయబడిన బ్రౌజర్ చరిత్రను లేదా బుక్‌మార్క్‌లను సవరించడానికి యాప్‌ను అనుమతిస్తుంది. ఇది బ్రౌజర్ డేటాను ఎరేజ్ చేయడానికి లేదా సవరించడానికి యాప్‌ను అనుమతించవచ్చు. గమనిక: ఈ అనుమతి మూడవ పక్షం బ్రౌజర్‌లు లేదా వెబ్ బ్రౌజింగ్ సామర్థ్యాలు గల ఇతర యాప్‌ల ద్వారా అమలు చేయబడకపోవచ్చు."</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g>ని సిద్ధం చేస్తోంది."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"యాప్‌లను ప్రారంభిస్తోంది."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"బూట్‌ను ముగిస్తోంది."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"స్క్రీన్‌ను ఆఫ్ చేయాలా?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"మీ వేలిముద్రను సెటప్ చేస్తున్నప్పుడు, మీరు పవర్ బటన్‌ను నొక్కారు.\n\nఇది సాధారణంగా మీ స్క్రీన్‌ను ఆఫ్ చేస్తుంది."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ఆఫ్ చేయండి"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"రద్దు చేయండి"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"సెటప్‌ను కొనసాగించాలా?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"మీరు పవర్ బటన్‌ను నొక్కారు — ఇది సాధారణంగా స్క్రీన్‌ను ఆఫ్ చేస్తుంది.\n\nమీ వేలిముద్రను సెటప్ చేస్తున్నప్పుడు తేలికగా ట్యాప్ చేయడానికి ట్రై చేయండి."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"స్క్రీన్‌ను ఆఫ్ చేయి"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"సెటప్‌ను కొనసాగించు"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"కొనసాగించండి"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> అమలవుతోంది"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"గేమ్‌కి తిరిగి రావడానికి నొక్కండి"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"గేమ్‌ను ఎంచుకోండి"</string>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 606d0f2..92bea34 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -27,11 +27,11 @@
     <!-- The percentage of the screen width to use for the default width or height of
          picture-in-picture windows. Regardless of the percent set here, calculated size will never
          be smaller than @dimen/default_minimal_size_pip_resizable_task. -->
-    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.14</item>
+    <item name="config_pictureInPictureDefaultSizePercent" format="float" type="dimen">0.2</item>
 
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
-    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">56x27</string>
+    <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">24x24</string>
 
     <!-- The default gravity for the picture-in-picture window.
          Currently, this maps to Gravity.BOTTOM | Gravity.RIGHT -->
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index b8d8506..41adb26 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"อนุญาตให้แอปใช้บริการ IMS เพื่อโทรออกโดยคุณไม่ต้องดำเนินการใดๆ เลย"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"อ่านสถานะและข้อมูลระบุตัวตนของโทรศัพท์"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"อนุญาตให้แอปพลิเคชันเข้าถึงฟีเจอร์โทรศัพท์ของอุปกรณ์ การอนุญาตนี้ทำให้แอปพลิเคชันสามารถตรวจสอบหมายเลขโทรศัพท์และรหัสอุปกรณ์ ตรวจสอบว่ามีการโทรที่ทำงานอยู่หรือไม่ และตรวจสอบหมายเลขระยะไกลที่เชื่อมต่อด้วยการโทร"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"อ่านสถานะการใช้โทรศัพท์เบื้องต้นและข้อมูลระบุตัวตน"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"อนุญาตให้แอปเข้าถึงฟีเจอร์พื้นฐานในการใช้โทรศัพท์ของอุปกรณ์นี้"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"กำหนดเส้นทางการโทรผ่านระบบ"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"อนุญาตให้แอปกำหนดเส้นทางการโทรของแอปผ่านระบบเพื่อปรับปรุงประสบการณ์ในการโทร"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"ดูและจัดการการติดต่อผ่านระบบ"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"แตะเพื่อลบรูปแบบใบหน้า แล้วเพิ่มใบหน้าอีกครั้ง"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"ตั้งค่าการปลดล็อกด้วยใบหน้า"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"ปลดล็อกโทรศัพท์โดยมองไปที่โทรศัพท์"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"หากต้องการใช้ปลดล็อกด้วยใบหน้า ให้เปิด"<b>"การเข้าถึงกล้อง"</b>"ในการตั้งค่าและความเป็นส่วนตัว"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"ตั้งค่าการปลดล็อกด้วยวิธีอื่น"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"แตะเพื่อเพิ่มลายนิ้วมือ"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"ปลดล็อกด้วยลายนิ้วมือ"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"กำลังเตรียม <xliff:g id="APPNAME">%1$s</xliff:g>"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"กำลังเริ่มต้นแอปพลิเคชัน"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"เสร็จสิ้นการบูต"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"ปิดหน้าจอใช่ไหม"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"ขณะตั้งค่าลายนิ้วมือคุณจะต้องกดปุ่มเปิด/ปิด\n\nซึ่งโดยปกติจะเป็นการปิดหน้าจอด้วย"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"ปิด"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"ยกเลิก"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"ตั้งค่าต่อไหม"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"คุณกดปุ่มเปิด/ปิดซึ่งโดยปกติจะเป็นการปิดหน้าจอ\n\nลองแตะเบาๆ ขณะตั้งค่าลายนิ้วมือ"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"ปิดหน้าจอ"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"ตั้งค่าต่อ"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"ต่อไป"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> กำลังทำงาน"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"แตะเพื่อกลับไปที่เกม"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"เลือกเกม"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 30350a5..7f39769 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Pinapahintulutan ang app na gamitin ang serbisyo ng IMS upang tumawag nang walang pahintulot mo."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"basahin ang katayuan at pagkakakilanlan ng telepono"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Pinapayagan ang app na i-access ang mga tampok ng telepono ng device. Pinapayagan ng pahintulot na ito ang app na tukuyin ang numero ng telepono at  mga ID ng device, kung aktibo man ang isang tawag, at ang malayuang numerong ikinonekta ng isang tawag."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"basahin ang pangunahing status at pagkakakilanlan sa telephony"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Pinapayagan ang app na i-access ang mga pangunahing feature sa telephony ng device."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"iruta ang mga tawag sa pamamagitan ng system"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Pinapayagan ang app na iruta ang mga tawag nito sa pamamagitan ng system upang mapahusay ang karanasan sa pagtawag."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"tingnan at kontrolin ang mga tawag sa pamamagitan ng system."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"I-tap para i-delete ang iyong face model, pagkatapos ay idagdag ulit ang mukha mo"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"I-set up ang Pag-unlock Gamit ang Mukha"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"I-unlock ang iyong telepono sa pamamagitan ng pagtingin dito"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Para magamit ang Pag-unlock Gamit ang Mukha, i-on ang "<b>"Access sa camera"</b>" sa Mga Setting &gt; Privacy"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Mag-set up ng higit pang paraan para mag-unlock"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"I-tap para magdagdag ng fingerprint"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Pag-unlock Gamit ang Fingerprint"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Ihinahanda ang <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Sinisimulan ang apps."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Pagtatapos ng pag-boot."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"I-off ang screen?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Habang sine-set up ang iyong fingerprint, pinindot mo ang Power button.\n\nKaraniwan nitong ino-off ang iyong screen."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"I-off"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Kanselahin"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Ituloy ang pag-set up?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Pinindot mo ang power button — karaniwan nitong ino-off ang screen.\n\nSubukang i-tap habang sine-set up ang iyong fingerprint."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"I-off ang screen"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Ituloy ang setup"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Magpatuloy sa pag-verify ng fingerprint?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Pinindot mo ang power button — karaniwan nitong ino-off ang screen.\n\nSubukang i-tap para i-verify ang iyong fingerprint."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"I-off ang screen"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Magpatuloy"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Tumatakbo ang <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Mag-tap upang bumalik sa laro"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Pumili ng laro"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index e676c89..bc26fed 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Uygulamanın, sizin müdahaleniz olmadan telefon etmek için IMS hizmetini kullanmasına izin verir."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"telefonun durumunu ve kimliğini okuma"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Uygulamaya cihazdaki telefon özelliklerine erişme izni verir. Bu izin, uygulamanın telefon numarasını ve cihaz kimliğini, etkin bir çağrı olup olmadığını ve çağrıda bağlanılan karşı tarafın numarasını öğrenmesine olanak sağlar."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"temel telefon durumunu ve kimliğini okuma"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Uygulamanın, cihazdaki temel telefon özelliklerine erişmesine izin verir."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"aramaları sistem üzerinden yönlendir"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Uygulamanın, çağrı deneyimini iyileştirmek için çağrılarını sistem üzerinden yönlendirmesine olanak tanır."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"aramaları sistemde görüp denetleme."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Yüz modelinizi silmek için dokunup ardından yüzünüzü yeniden ekleyin"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Yüz Tanıma Kilidi\'ni kurma"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Telefonunuza bakarak kilidini açın"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Yüz Tanıma Kilidi\'ni kullanmak için Ayarlar &gt; Gizlilik bölümünden "<b>"Kamera erişimi"</b>"\'ni açın"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Kilidi açmak için daha fazla yöntem ayarlayın"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Parmak izi eklemek için dokunun"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Parmak İzi Kilidi"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> hazırlanıyor."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Uygulamalar başlatılıyor"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Açılış tamamlanıyor."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Ekran kapatılsın mı?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Parmak izinizi ayarlarken Güç düğmesine bastınız.\n\nBu hareket genellikle ekranınızın kapanmasına neden olur."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Kapat"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"İptal"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Kuruluma devam edilsin mi?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Güç düğmesine bastınız. Bu düğmeye basıldığında genellikle ekran kapanır.\n\nParmak izinizi tanımlarken hafifçe dokunmayı deneyin."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Ekranı kapat"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Kuruluma devam et"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Parmak izi doğrulamaya devam edilsin mi?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Güç düğmesine bastınız. Bu düğmeye basıldığında genellikle ekran kapanır.\n\nParmak izinizi doğrulamak için hafifçe dokunmayı deneyin."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Ekranı kapat"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Devam"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Oyuna geri dönmek için dokunun"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Oyun seçin"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 18c5600..5ce1407 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -477,6 +477,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Додаток зможе телефонувати за допомогою служби IMS без вашого відома."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"читати статус та ідентифікаційну інформацію телефону"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Дозволяє програмі отримувати доступ до телефонних функцій пристрою. Такий дозвіл дає програмі змогу визначати номер телефону й ідентифікатори пристрою, активність виклику, а також віддалений номер, на який здійснюється виклик."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"зчитувати статус та ідентифікаційну інформацію телефонії"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Дозволяє додатку отримувати доступ до базових функцій телефонії на пристрої."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"маршрутизувати виклики через систему"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Дозволяє додатку маршрутизувати виклики через систему, щоб було зручніше телефонувати."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"Переглядати виклики через систему й керувати ними."</string>
@@ -628,6 +630,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Натисніть, щоб видалити свою модель обличчя, а потім знову додайте її"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Налаштування фейсконтролю"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Ви зможете розблоковувати телефон, подивившись на нього"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Щоб використовувати фейсконтроль, увімкніть "<b>"Доступ до камери"</b>" в розділі \"Налаштування\" &gt; \"Конфіденційність\""</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Налаштуйте більше способів розблокування"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Натисніть, щоб додати відбиток пальця"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Розблокування відбитком пальця"</string>
@@ -1316,10 +1319,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Підготовка додатка <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Запуск програм."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Завершення завантаження."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Вимкнути екран?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Налаштовуючи відбиток пальця, ви натиснули кнопку живлення.\n\nЗазвичай після цього вимикається екран."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Вимкнути"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Скасувати"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Продовжити реєстрацію?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Ви натиснули кнопку живлення – зазвичай після цього вимикається екран.\n\nЩоб зареєструвати відбиток пальця, спробуйте лише злегка торкнутися датчика."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Вимкнути екран"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Додати відбиток"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Продовжити"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"Працює <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Торкніться, щоб повернутися в гру"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Виберіть гру"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index fdb8ddd..7ceaa99 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"‏آپ کی مداخلت کے بغیر کالیں کرنے کیلئے ایپ کو IMS سروس استعمال کرنے کی اجازت دیتی ہے۔"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"فون کے اسٹیٹس اور شناخت کو پڑھیں"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"‏ایپ کو آلے کی فون والی خصوصیات تک رسائی حاصل کرنے کی اجازت دیتا ہے۔ یہ اجازت ایپ کو فون نمبر اور آلے کے IDs کا تعین کرنے، آیا کوئی کال فعال ہے، اور کال کے ذریعہ مربوط ریموٹ نمبر کا تعین کرنے دیتی ہے۔"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"بنیادی ٹیلیفونی صورتحال اور شناخت کو پڑھیں"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"اس سے ایپ کو آلے کی بنیادی ٹیلیفونی خصوصیات تک رسائی کی اجازت ملتی ہے۔"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"سسٹم کے ذریعہ کالز روٹ کریں"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"کالںگ کا تجربہ بہتر بنانے کے لیے سسٹم کے ذریعہ ایپ کو کالز روٹ کرنے کی اجازت دیتا ہے۔"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"سسٹم کے ذریعے کالز دیکھیں اور کنٹرول کریں۔"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"اپنے چہرے کا ماڈل حذف کرنے کے لیے تھپتھپائیں پھر اپنا چہرہ دوبارہ شامل کریں"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"فیس اَنلاک سیٹ اپ کریں"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"اپنے فون کی طرف دیکھ کر اسے غیر مقفل کریں"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"فیس اَنلاک کا استعمال کرنے کے لیے، ترتیبات اور رازداری میں "<b>"کیمرے تک رسائی"</b>" کو آن کریں"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"غیر مقفل کرنے کے مزید طریقے سیٹ اپ کریں"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"فنگر پرنٹ شامل کرنے کیلئے تھپتھپائیں"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"فنگر پرنٹ اَن لاک"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> تیار ہو رہی ہے۔"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"ایپس شروع ہو رہی ہیں۔"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"بوٹ مکمل ہو رہا ہے۔"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"اسکرین آف کریں؟"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"اپنی فنگر پرنٹ سیٹ اپ کرنے کے دوران آپ نے پاور بٹن دبایا۔\n\n یہ عام طور پر آپ کی اسکرین کو آف کرتی ہے۔"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"آف کریں"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"منسوخ کریں"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"سیٹ اپ جاری رکھیں؟"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"آپ نے پاور بٹن دبایا — اس سے عام طور پر اسکرین آف ہو جاتی ہے۔\n\nاپنے فنگر پرنٹ کو سیٹ اپ کرنے کے دوران ہلکا سا تھپتھپانے کی کوشش کریں۔"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"اسکرین آف کریں"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"سیٹ اپ جاری رکھیں"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"جاری رکھیں"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> چل رہی ہے"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"گیم پر واپس جانے کے لیے تھپتھپائیں"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"گیم کا انتخاب کریں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 73cc496..a40b67f 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Ilovaga sizning ishtirokingizsiz qo‘ng‘iroqlarni amalga oshirish uchun IMS xizmatidan foydalanishga ruxsat beradi."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"telefon holati haqidagi ma’lumotlarni olish"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Ilovaga qurilmangizdagi telefon xususiyatlariga kirishga ruxsat beradi. Bu ruxsat ilovaga telefon raqami va qurilma nomlari, qo‘ng‘iroq faol yoki faolsizligi va masofadagi raqam qo‘ng‘rioq orqali bog‘langanligini aniqlashga imkon beradi."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"oddiy telefoniya holatini oʻqish va aniqlash"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Ilovaga qurilmadagi oddiy telefoniya funksiyalaridan foydalanish imkonini beradi."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"chaqiruvlarni tizim orqali yo‘naltirish"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Ilova aloqa sifatini yaxshilash maqsadida chaqiruvlarni tizim orqali yo‘naltirishi mumkin."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"tizim orqali chaqiruvlarni tekshirish va boshqarish."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Yuz modelini oʻchirish uchun bosing va keyin yana yuzni qoʻshing"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Yuz bilan ochishni sozlash"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Telefoningizni yuz tekshiruvi yordamida qulfdan chiqaring"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Yuz bilan ochish uchun Sozlamalar va maxfiylik orqali "<b>"kameraga kirishga ruxsat bering"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Qulfdan chiqarishning boshqa usullarini sozlang"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Barmoq izi kiritish uchun bosing"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Barmoq izi bilan ochish"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"<xliff:g id="APPNAME">%1$s</xliff:g> tayyorlanmoqda."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Ilovalar ishga tushirilmoqda."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Tizimni yuklashni tugatish."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Ekran oʻchirilsinmi?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Barmoq izini sozlayotganda Quvvat tugmasini bosdingiz.\n\nBunda odatda ekran oʻchib qoladi."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Oʻchirish"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Bekor qilish"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Sozlashni davom ettirasizmi?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Quvvat tugmasini bosdingiz — bu odatda ekranni oʻchiradi.\n\nBarmoq izini qoʻshish vaqtida tugmaga yengilgina tegining."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Ekranni oʻchirish"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Sozlashni davom ettirish"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Barmoq izi tasdiqlashda davom etilsinmi?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Quvvat tugmasini bosdingiz. Bu odatda ekranni oʻchiradi.\n\nBarmoq izingizni tasdiqlash uchun tugmaga yengilgina tegining."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Ekranni oʻchirish"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Davom etish"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> ishlamoqda"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"O‘yinga qaytish uchun bosing"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"O‘yinni tanlang"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index d47b4f4..1731b3b 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Cho phép ứng dụng sử dụng dịch vụ IMS để thực hiện cuộc gọi mà không có sự can thiệp của bạn."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"đọc trạng thái và nhận dạng của điện thoại"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Cho phép ứng dụng truy cập vào các tính năng điện thoại của thiết bị. Quyền này cho phép ứng dụng xác định số điện thoại và ID thiết bị, cho dù cuộc gọi có hiện hoạt hay không và số từ xa có được kết nối bằng một cuộc gọi hay không."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"đọc danh tính và trạng thái cơ bản của điện thoại"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Cho phép ứng dụng dùng các tính năng cơ bản của điện thoại trên thiết bị."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"định tuyến cuộc gọi thông qua hệ thống"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Cho phép ứng dụng định tuyến cuộc gọi thông qua hệ thống nhằm cải thiện trải nghiệm gọi điện."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"xem và kiểm soát cuộc gọi thông qua hệ thống."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Nhấn để xóa mẫu khuôn mặt, sau đó thêm lại khuôn mặt của bạn"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Thiết lập tính năng Mở khóa bằng khuôn mặt"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Mở khóa điện thoại bằng cách nhìn vào điện thoại"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Để dùng tính năng Mở khoá bằng khuôn mặt, hãy bật tuỳ chọn "<b>"Truy cập máy ảnh"</b>" trong phần Cài đặt &gt; Quyền riêng tư"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Thiết lập thêm những cách mở khóa khác"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Nhấn để thêm vân tay"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Mở khóa bằng vân tay"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Đang chuẩn bị <xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Khởi động ứng dụng."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Hoàn tất khởi động."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Tắt màn hình?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Trong khi thiết lập vân tay, bạn đã nhấn vào nút Nguồn.\n\nThông thường, thao tác này sẽ tắt màn hình."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Tắt"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Hủy"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Tiếp tục thiết lập?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Bạn đã nhấn nút nguồn – thao tác này thường tắt màn hình.\n\nHãy thử nhấn nhẹ khi thiết lập vân tay của bạn."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Tắt màn hình"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Tiếp tục thiết lập"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Tiếp tục xác minh vân tay của bạn?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Bạn đã nhấn nút nguồn – thao tác này thường tắt màn hình.\n\nHãy thử nhấn nhẹ để xác minh vân tay."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Tắt màn hình"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Tiếp tục"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> đang hoạt động"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Nhấn để quay lại trò chơi"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Chọn trò chơi"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 17ce14b..8ce9ab2 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"允许应用自行使用即时通讯服务拨打电话。"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"读取手机状态和身份"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"允许该应用访问设备的电话功能。此权限可让该应用确定本机号码和设备 ID、是否正处于通话状态以及拨打的号码。"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"读取基本电话状态和身份信息"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"允许该应用使用设备的基本电话功能。"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"通过系统转接来电"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"允许该应用通过系统转接来电,以改善通话体验。"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"查看并控制通过系统拨打的电话。"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"请点按以删除您的脸部模型,然后再添加您的脸部模型"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"设置人脸解锁"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"脸部对准手机即可将其解锁"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"如需使用人脸解锁功能,请在“设置”&gt;“隐私权”中开启"<b>"摄像头使用权限"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"设置更多解锁方式"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"点按即可添加指纹"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"指纹解锁"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"正在准备升级<xliff:g id="APPNAME">%1$s</xliff:g>。"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"正在启动应用。"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"即将完成启动。"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"要关闭屏幕吗?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"您在设置指纹时按了电源按钮。\n\n此操作通常会关闭屏幕。"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"关闭"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"取消"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"要继续设置吗?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"您已按电源按钮,这通常会关闭屏幕。\n\n请尝试在设置指纹时轻轻按一下。"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"关闭屏幕"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"继续设置"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"继续"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"点按即可返回游戏"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"选择游戏"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 35a11b2..49850e14 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"允許應用程式自行使用 IMS 服務撥打電話。"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"讀取手機狀態和識別碼"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"允許應用程式使用裝置的電話功能。這項權限允許應用程式確定手機號碼和裝置編號、是否正在通話中,以及所撥打的對方號碼。"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"讀取基本電話狀態和身分"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"允許應用程式存取裝置的基本電話功能。"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"透過系統轉接來電"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"允許應用程式透過系統轉接來電,以改善通話體驗。"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"透過系統查看和控制通話。"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"請輕按這裡刪除面部模型,然後再重新新增"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"設定「面孔解鎖」"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"直望手機即可解鎖"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"如要使用「面孔解鎖」,請在 [設定] &gt; [私隱] 開啟"<b>"相機存取權"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"設定更多解鎖方法"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"輕按即可新增指紋"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"指紋解鎖"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"正在準備 <xliff:g id="APPNAME">%1$s</xliff:g>。"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"正在啟動應用程式。"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"啟動完成。"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"要關閉螢幕嗎?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"您在設定指紋時按了開關按鈕。\n\n螢幕通常會因此而關閉。"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"關閉"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"取消"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"要繼續設定嗎?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"您已按下開關按鈕,這麼做通常會關閉螢幕。\n\n設定指紋時請嘗試輕按開關按鈕。"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"關閉螢幕"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"繼續設定"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"繼續"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"正在執行 <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"輕按即可返回遊戲"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"選擇遊戲"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 42f0586..329b5dc 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"允許應用程式自動使用 IMS 服務撥打電話。"</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"讀取手機狀態和識別碼"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"允許應用程式使用裝置的電話功能。這項權限可讓應用程式判讀手機號碼和裝置 ID、是否正在通話中,以及所撥打的對方號碼。"</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"讀取基本電話通訊狀態和識別資訊"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"允許應用程式存取裝置的基本電話通訊功能。"</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"透過系統接通來電"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"允許應用程式透過系統接通來電,以改善通話品質。"</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"查看及控管透過系統撥打的電話。"</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"請輕觸這裡刪除臉部模型,然後再重新新增"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"設定人臉解鎖功能"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"看著手機就能解鎖"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"如要使用人臉解鎖功能,請前往「設定」&gt;「隱私權」開啟"<b>"攝影機存取權"</b></string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"設定更多解鎖方式"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"輕觸即可新增指紋"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"指紋解鎖"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"正在準備升級「<xliff:g id="APPNAME">%1$s</xliff:g>」。"</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"正在啟動應用程式。"</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"啟動完成。"</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"要關閉螢幕嗎?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"設定指紋時必須按下電源鍵。\n\n這項操作通常會將螢幕關閉。"</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"關閉"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"取消"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"要繼續設定嗎?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"你已按下電源鍵,這麼做通常會關閉螢幕。\n\n設定指紋時請試著減輕觸碰電源鍵的力道。"</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"關閉螢幕"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"繼續設定"</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>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"繼續"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"輕觸即可返回遊戲"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"選擇遊戲"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 3f7e37d..8c7d5ea 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -471,6 +471,8 @@
     <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"Ivumela uhlelo lokusebenza ukuthi lusebenzise isevisi ye-IMS ukuze yenze amakholi ngaphandle kokungenelela kwakho."</string>
     <string name="permlab_readPhoneState" msgid="8138526903259297969">"funda isimo sefoni kanye nesazisi"</string>
     <string name="permdesc_readPhoneState" msgid="7229063553502788058">"Ivumela uhlelo lokusebenza ukufinyelela izici zefoni zedivayisi. Le mvume ivumela uhlelo lokusebenza ukucacisa inombolo yefoni nobunikazi bedivayisi, ukuthi noma ikholi iyasebenza, futhi nenombolo yesilawuli kude zixhunywe ngekholi."</string>
+    <string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"funda isimo nokuhlonza ucingo okuyisisekelo"</string>
+    <string name="permdesc_readBasicPhoneState" msgid="828185691675460520">"Ivumela i-app ukufinyelela izakhi zocingo eziyisisekelo zedivayisi."</string>
     <string name="permlab_manageOwnCalls" msgid="9033349060307561370">"yanza imizila yamakholi ngesistimu"</string>
     <string name="permdesc_manageOwnCalls" msgid="4431178362202142574">"Ivumela uhlelo lokusebenza ukwenza imizila yamakholi ngesistimu ukuze ithuthukise umuzwa wokushaya."</string>
     <string name="permlab_callCompanionApp" msgid="3654373653014126884">"bona futhi ulawule amakholi ngesistimu."</string>
@@ -622,6 +624,7 @@
     <string name="face_recalibrate_notification_content" msgid="3064513770251355594">"Thepha ukuze usule imodeli yakho yobuso, bese wengeza futhi ubuso"</string>
     <string name="face_setup_notification_title" msgid="8843461561970741790">"Setha Ukuvula ngobuso"</string>
     <string name="face_setup_notification_content" msgid="5463999831057751676">"Vula ifoni yakho ngokuyibheka"</string>
+    <string name="face_sensor_privacy_enabled" msgid="7407126963510598508">"Ukuze usebenzise Ukuvula ngobuso, vula "<b>"Ukufinyelela kwekhamera"</b>" kokuthi Amasethingi &gt; Ubumfihlo"</string>
     <string name="fingerprint_setup_notification_title" msgid="2002630611398849495">"Setha izindlela eziningi zokuvula"</string>
     <string name="fingerprint_setup_notification_content" msgid="205578121848324852">"Thepha ukuze ungeze izigxivizo zomunwe"</string>
     <string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Ukuvula ngesigxivizo somunwe"</string>
@@ -1276,10 +1279,14 @@
     <string name="android_preparing_apk" msgid="589736917792300956">"Ukulungisela i-<xliff:g id="APPNAME">%1$s</xliff:g>."</string>
     <string name="android_upgrading_starting_apps" msgid="6206161195076057075">"Qalisa izinhlelo zokusebenza."</string>
     <string name="android_upgrading_complete" msgid="409800058018374746">"Qedela ukuqala kabusha."</string>
-    <string name="fp_enrollment_powerbutton_intent_title" msgid="3385634173366119903">"Vala isikrini?"</string>
-    <string name="fp_enrollment_powerbutton_intent_message" msgid="6582149052513682522">"Lapho usetha izigxivizo zeminwe yakho, ucindezele Inkinobho yamandla.\n\nLokhu kuvame ukuvala isikrini sakho."</string>
-    <string name="fp_enrollment_powerbutton_intent_positive_button" msgid="5963520983910436791">"Vala"</string>
-    <string name="fp_enrollment_powerbutton_intent_negative_button" msgid="6465764183480190748">"Khansela"</string>
+    <string name="fp_power_button_enrollment_title" msgid="3574363228413259548">"Qhubeka nokusetha?"</string>
+    <string name="fp_power_button_enrollment_message" msgid="5648173517663246140">"Ucindezele inkinobho yamandla — lokhu kuvame ukuvala isikrini.\n\nZama ukuthepha kancane ngenkathi usetha isigxivizo sakho somunwe."</string>
+    <string name="fp_power_button_enrollment_positive_button" msgid="2095415838459356833">"Vala isikrini"</string>
+    <string name="fp_power_button_enrollment_negative_button" msgid="6558436406362486747">"Qhubeka nokusetha"</string>
+    <string name="fp_power_button_bp_title" msgid="5585506104526820067">"Qhubeka uqinisekise isigxivizo sakho somunwe?"</string>
+    <string name="fp_power_button_bp_message" msgid="2983163038168903393">"Ucindezele inkinobho yamandla — lokhu kuvame ukuvala isikrini.\n\nZama ukuthepha kancane ukuze uqinisekise isigxivizo sakho somunwe."</string>
+    <string name="fp_power_button_bp_positive_button" msgid="728945472408552251">"Vala isikrini"</string>
+    <string name="fp_power_button_bp_negative_button" msgid="3971364246496775178">"Qhubeka"</string>
     <string name="heavy_weight_notification" msgid="8382784283600329576">"<xliff:g id="APP">%1$s</xliff:g> iyasebenza"</string>
     <string name="heavy_weight_notification_detail" msgid="6802247239468404078">"Thepha ukuze ubuyele emuva kwigeyimu"</string>
     <string name="heavy_weight_switcher_title" msgid="3861984210040100886">"Khetha igeyimu"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 697ec20..8aa9201 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1329,6 +1329,11 @@
              dictionary-based word suggestions.  Corresponds to
              {@link android.text.InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS}. -->
         <flag name="textNoSuggestions" value="0x00080001" />
+        <!-- Can be combined with <var>text</var> and its variations to
+             indicate that if there is extra information, the IME should provide
+             {@link android.view.inputmethod.TextAttribute}.  Corresponds to
+             {@link android.text.InputType#TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS}. -->
+        <flag name="textEnableTextConversionSuggestions" value="0x00100001" />
         <!-- Text that will be used as a URI.  Corresponds to
              {@link android.text.InputType#TYPE_CLASS_TEXT} |
              {@link android.text.InputType#TYPE_TEXT_VARIATION_URI}. -->
@@ -3633,6 +3638,9 @@
              to re-retrieve all resources (including view layouts, drawables, etc)
              to correctly handle any configuration change.-->
         <attr name="configChanges" />
+        <!-- Specifies whether the IME supports Handwriting using stylus. Defaults to false. -->
+        <attr name="supportsStylusHandwriting" format="boolean" />
+
     </declare-styleable>
 
     <!-- This is the subtype of InputMethod. Subtype can describe locales (for example, en_US and
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index fe58114..06f347f 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -550,7 +550,7 @@
     <attr name="allowTaskReparenting" format="boolean" />
 
     <!-- Declare that this application may use cleartext traffic, such as HTTP rather than HTTPS;
-         WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or TLS.
+         WebSockets rather than WebSockets Secure; XMPP, IMAP, SMTP without STARTTLS or TLS.
          Defaults to true. If set to false {@code false}, the application declares that it does not
          intend to use cleartext network traffic, in which case platform components (e.g. HTTP
          stacks, {@code DownloadManager}, {@code MediaPlayer}) will refuse applications's requests
@@ -1785,7 +1785,7 @@
         <!-- @deprecated replaced by setting appCategory attribute to "game" -->
         <attr name="isGame" />
         <!-- Declare that this application may use cleartext traffic, such as HTTP rather than
-             HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, STMP without STARTTLS or
+             HTTPS; WebSockets rather than WebSockets Secure; XMPP, IMAP, SMTP without STARTTLS or
              TLS). Defaults to true. If set to false {@code false}, the application declares that it
              does not intend to use cleartext network traffic, in which case platform components
              (e.g. HTTP stacks, {@code DownloadManager}, {@code MediaPlayer}) will refuse
@@ -2584,6 +2584,9 @@
     <declare-styleable name="AndroidManifestProcess" parent="AndroidManifestProcesses">
         <!-- Required name of the process that is allowed -->
         <attr name="process" />
+        <!-- custom Application class name. We use call it "name", not "className", to be
+             consistent with the Application tag. -->
+        <attr name="name" />
         <attr name="gwpAsanMode" />
         <attr name="memtagMode" />
         <attr name="nativeHeapZeroInitialized" />
@@ -2808,6 +2811,22 @@
         <attr name="attributionTags" />
     </declare-styleable>
 
+    <!-- @hide The <code>apex-system-service</code> tag declares an apex system service
+         that is contained within an application.
+
+         The apex system service tag appears as a child tag of the
+         {@link #AndroidManifestApplication application} tag. -->
+    <declare-styleable name="AndroidManifestApexSystemService"
+                       parent="AndroidManifestApplication">
+        <!-- The fully qualified class name of the system service. -->
+        <attr name="name" />
+        <!-- The filepath to the .jar that contains the system service. If this is not provided, it
+             is assumed that the system service exists in SYSTEMSERVERCLASSPATH. -->
+        <attr name="path" />
+        <attr name="minSdkVersion" />
+        <attr name="maxSdkVersion" />
+    </declare-styleable>
+
     <!-- The <code>receiver</code> tag declares an
          {@link android.content.BroadcastReceiver} class that is available
          as part of the package's application components, allowing the
@@ -3051,6 +3070,7 @@
     <declare-styleable name="AndroidManifestMetaData"
          parent="AndroidManifestApplication
                  AndroidManifestActivity
+                 AndroidManifestApexSystemService
                  AndroidManifestReceiver
                  AndroidManifestProvider
                  AndroidManifestService
@@ -3085,6 +3105,7 @@
     <declare-styleable name="AndroidManifestProperty"
          parent="AndroidManifestApplication
                  AndroidManifestActivity
+                 AndroidManifestApexSystemService
                  AndroidManifestReceiver
                  AndroidManifestProvider
                  AndroidManifestService">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ee21d91..050d20e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2110,6 +2110,8 @@
     <string name="config_systemWifiCoexManager" translatable="false"></string>
     <!-- The name of the package that will hold the wellbeing role. -->
     <string name="config_systemWellbeing" translatable="false"></string>
+    <!-- The name of the package that will hold the game service role. -->
+    <string name="config_systemGameService" translatable="false"></string>
     <!-- The name of the package that will hold the television notification handler role -->
     <string name="config_systemTelevisionNotificationHandler" translatable="false"></string>
     <!-- The name of the package that will hold the system activity recognizer role. -->
@@ -2121,6 +2123,8 @@
     <string name="config_systemTelevisionRemoteService" translatable="false">@string/config_tvRemoteServicePackage</string>
     <!-- The name of the package that will hold the device management role -->
     <string name="config_deviceManager" translatable="false"></string>
+    <!-- The name of the package that will hold the app protection service role. -->
+    <string name="config_systemAppProtectionService" translatable="false"></string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false">com.android.stk</string>
@@ -2379,7 +2383,7 @@
     <!-- If supported and enabled, are dreams activated when asleep and charging? (by default) -->
     <bool name="config_dreamsActivatedOnSleepByDefault">false</bool>
     <!-- ComponentName of the default dream (Settings.Secure.DEFAULT_SCREENSAVER_COMPONENT) -->
-    <string name="config_dreamsDefaultComponent" translatable="false">com.google.android.deskclock/com.android.deskclock.Screensaver</string>
+    <string name="config_dreamsDefaultComponent" translatable="false">com.android.deskclock/com.android.deskclock.Screensaver</string>
 
     <!-- Are we allowed to dream while not plugged in? -->
     <bool name="config_dreamsEnabledOnBattery">false</bool>
@@ -3329,6 +3333,20 @@
          and one pSIM) -->
     <integer name="config_num_physical_slots">1</integer>
 
+    <!--The default "usage setting" indicating that the device is either a voice-centric
+    device (1) or a data-centric device (2). A voice-centric device will require that any cellular
+    service that it uses provides access to voice capability, and a data-centric device will
+    likewise require that the network provides access to data services. These settings are
+    sent to the cellular modem and control the behavior in accordance with 3gpp TS 24.301 sec 4.3
+    (and equivalent functionality in other generations of cellular).-->
+    <integer name="config_default_cellular_usage_setting">1</integer>
+
+    <!--The list of supported cellular usage settings for this device.-->
+    <integer-array translatable="false" name="config_supported_cellular_usage_settings">
+        <item>1</item>    <!-- USAGE_SETTING_VOICE_CENTRIC -->
+        <item>2</item>    <!-- USAGE_SETTING_DATA_CENTRIC -->
+    </integer-array>
+
     <!-- When a radio power off request is received, we will delay completing the request until
          either IMS moves to the deregistered state or the timeout defined by this configuration
          elapses. If 0, this feature is disabled and we do not delay radio power off requests.-->
@@ -4015,6 +4033,11 @@
     -->
     <string name="config_defaultSystemCaptionsManagerService" translatable="false"></string>
 
+    <!-- Indicates whether the system wide captions service should also support
+         call captioning.
+    -->
+    <bool name="config_systemCaptionsServiceCallsEnabled" translatable="false"></bool>
+
     <!-- The package name for the incident report approver app.
         This app is usually PermissionController or an app that replaces it.  When
         a bugreport or incident report with EXPLICT-level sharing flags is going to be
@@ -5506,4 +5529,21 @@
     <string name="config_work_badge_path_24" translatable="false">
         M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z
     </string>
+
+    <!-- GNSS configurations to override carrier config. Empty by default-->
+    <string-array name="config_gnssParameters" translatable="false">
+        <!-- Add configurations here, example: -->
+        <!-- <item>SUPL_HOST=supl.google.com</item> -->
+    </string-array>
+
+    <integer name="config_chooser_max_targets_per_row">4</integer>
+
+    <!-- Package that provides the supervised user creation flow. This package must include an
+         activity with an intent filter for {@link UserManager.ACTION_CREATE_SUPERVISED_USER}.
+         When this resource is defined, an extra button in user settings screen will be shown
+         with a title defined in @*android:string/supervised_user_creation_label
+         and an icon defined in @*android:drawable/ic_add_supervised_user.
+         That button will fire an intent targeted for this package with the mentioned action.
+         When this resource is empty, that button will not be shown. -->
+    <string name="config_supervisedUserCreationPackage" translatable="false"></string>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c40ec4f..a877bd3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -899,7 +899,8 @@
     <dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
 
     <!-- chooser/resolver (sharesheet) spacing -->
-    <dimen name="chooser_corner_radius">16dp</dimen>
+    <dimen name="chooser_width">412dp</dimen>
+    <dimen name="chooser_corner_radius">28dp</dimen>
     <dimen name="chooser_row_text_option_translate">25dp</dimen>
     <dimen name="chooser_view_spacing">18dp</dimen>
     <dimen name="chooser_edge_margin_thin">16dp</dimen>
@@ -916,7 +917,7 @@
     <dimen name="resolver_icon_size">32dp</dimen>
     <dimen name="resolver_button_bar_spacing">0dp</dimen>
     <dimen name="resolver_badge_size">18dp</dimen>
-    <dimen name="resolver_icon_margin">16dp</dimen>
+    <dimen name="resolver_icon_margin">8dp</dimen>
     <dimen name="resolver_small_margin">18dp</dimen>
     <dimen name="resolver_edge_margin">24dp</dimen>
     <dimen name="resolver_elevation">1dp</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 747a918..db348ed 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -275,6 +275,9 @@
   <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SWIPE_DOWN}. -->
   <item type="id" name="accessibilityActionSwipeDown" />
 
+  <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_SUGGESTIONS}. -->
+  <item type="id" name="accessibilityActionShowSuggestions" />
+
   <!-- View tag for remote views to store the index of the next child when adding nested remote views dynamically. -->
   <item type="id" name="remote_views_next_child" />
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index deb4d2d..3d3c860 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3249,6 +3249,7 @@
     <public name="canDisplayOnRemoteDevices" />
     <public name="supportedTypes" />
     <public name="resetEnabledSettingsOnAppDataCleared" />
+    <public name="supportsStylusHandwriting" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01de0000">
@@ -3256,6 +3257,7 @@
     <public name="accessibilityActionSwipeRight" />
     <public name="accessibilityActionSwipeUp" />
     <public name="accessibilityActionSwipeDown" />
+    <public name="accessibilityActionShowSuggestions" />
   </staging-public-group>
 
   <staging-public-group type="style" first-id="0x01dd0000">
@@ -3266,6 +3268,8 @@
     <public name="config_systemSupervision" />
     <!-- @hide @SystemApi -->
     <public name="config_deviceManager" />
+    <!-- @hide @SystemApi -->
+    <public name="config_systemAppProtectionService" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01db0000">
@@ -3305,6 +3309,8 @@
   </staging-public-group>
 
   <staging-public-group type="bool" first-id="0x01cf0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_systemCaptionsServiceCallsEnabled" />
   </staging-public-group>
 
   <staging-public-group type="fraction" first-id="0x01ce0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 688bced..b16e462 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1355,6 +1355,12 @@
       phone number and device IDs, whether a call is active, and the remote number
       connected by a call.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=80]-->
+    <string name="permlab_readBasicPhoneState">read basic telephony status and identity </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_readBasicPhoneState">Allows the app to access the basic telephony
+        features of the device.</string>
+
     <!-- Title of an application permission.  When granted the user is giving access to a third
          party app to route its calls through the system. -->
     <string name="permlab_manageOwnCalls">route calls through the system</string>
@@ -1756,6 +1762,8 @@
     <string name="face_setup_notification_title">Set up Face Unlock</string>
     <!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
     <string name="face_setup_notification_content">Unlock your phone by looking at it</string>
+    <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
+    <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
     <!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
     <string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
     <!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
@@ -5366,6 +5374,8 @@
     <string name="user_creation_account_exists">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> (a User with this account already exists) ?</string>
     <!-- Message to user that app is trying to create user for a specified account. [CHAR LIMIT=none] -->
     <string name="user_creation_adding">Allow <xliff:g id="app" example="Gmail">%1$s</xliff:g> to create a new User with <xliff:g id="account" example="foobar@gmail.com">%2$s</xliff:g> ?</string>
+    <!-- String label displayed on buttons that trigger the flow for creating supervised users. [CHAR LIMIT=35] -->
+    <string name="supervised_user_creation_label">Add supervised user</string>
 
     <!-- Locale picker strings -->
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 76e9774..504ad23 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,7 @@
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
   <java-symbol type="bool" name="config_avoidGfxAccel" />
   <java-symbol type="bool" name="config_bluetooth_address_validation" />
+  <java-symbol type="integer" name="config_chooser_max_targets_per_row" />
   <java-symbol type="bool" name="config_flipToScreenOffEnabled" />
   <java-symbol type="integer" name="config_flipToScreenOffMaxLatencyMicros" />
   <java-symbol type="bool" name="config_bluetooth_sco_off_call" />
@@ -491,6 +492,8 @@
   <java-symbol type="string" name="config_deviceSpecificDevicePolicyManagerService" />
   <java-symbol type="string" name="config_deviceSpecificAudioService" />
   <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" />
   <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
   <java-symbol type="array" name="config_integrityRuleProviderPackages" />
   <java-symbol type="bool" name="config_useAssistantVolume" />
@@ -2587,6 +2590,7 @@
   <java-symbol type="string" name="face_recalibrate_notification_name" />
   <java-symbol type="string" name="face_recalibrate_notification_title" />
   <java-symbol type="string" name="face_recalibrate_notification_content" />
+  <java-symbol type="string" name="face_sensor_privacy_enabled" />
   <java-symbol type="string" name="face_error_unable_to_process" />
   <java-symbol type="string" name="face_error_hw_not_available" />
   <java-symbol type="string" name="face_error_no_space" />
@@ -2860,6 +2864,7 @@
   <java-symbol type="layout" name="date_picker_month_item_material" />
   <java-symbol type="id" name="month_view" />
   <java-symbol type="integer" name="config_zen_repeat_callers_threshold" />
+  <java-symbol type="dimen" name="chooser_width" />
   <java-symbol type="dimen" name="chooser_corner_radius" />
   <java-symbol type="string" name="chooser_no_direct_share_targets" />
   <java-symbol type="drawable" name="chooser_row_layer_list" />
@@ -4621,6 +4626,11 @@
   <java-symbol type="array" name="config_roundedCornerBottomRadiusAdjustmentArray" />
   <java-symbol type="bool" name="config_secondaryBuiltInDisplayIsRound" />
   <java-symbol type="array" name="config_builtInDisplayIsRoundArray" />
+  <java-symbol type="array" name="config_gnssParameters" />
 
   <java-symbol type="integer" name="config_mashPressVibrateTimeOnPowerButton" />
+
+  <java-symbol type="string" name="config_systemGameService" />
+
+  <java-symbol type="string" name="config_supervisedUserCreationPackage"/>
 </resources>
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
index 59b4665..bd55426 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecConfigTest.java
@@ -17,7 +17,6 @@
 package android.bluetooth;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
 import junit.framework.TestCase;
 
@@ -34,7 +33,6 @@
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
-        BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX,
         BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID,
     };
     private static final int[] kCodecPriorityArray = new int[] {
@@ -168,20 +166,11 @@
             long codec_specific3 = selectCodecSpecific3(config_id);
             long codec_specific4 = selectCodecSpecific4(config_id);
 
-            BluetoothCodecConfig bcc = new BluetoothCodecConfig(codec_type, codec_priority,
+            BluetoothCodecConfig bcc = buildBluetoothCodecConfig(codec_type, codec_priority,
                                                                 sample_rate, bits_per_sample,
                                                                 channel_mode, codec_specific1,
                                                                 codec_specific2, codec_specific3,
                                                                 codec_specific4);
-            if (sample_rate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
-                assertFalse(bcc.isValid());
-            } else if (bits_per_sample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
-                assertFalse(bcc.isValid());
-            } else if (channel_mode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
-                assertFalse(bcc.isValid());
-            } else {
-                assertTrue(bcc.isValid());
-            }
 
             if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC) {
                 assertTrue(bcc.isMandatoryCodec());
@@ -204,10 +193,6 @@
             if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC) {
                 assertEquals("LDAC", bcc.getCodecName());
             }
-            if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX) {
-                assertEquals("UNKNOWN CODEC(" + BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX + ")",
-                             bcc.getCodecName());
-            }
             if (codec_type == BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
                 assertEquals("INVALID CODEC", bcc.getCodecName());
             }
@@ -227,7 +212,7 @@
     @SmallTest
     public void testBluetoothCodecConfig_equals() {
         BluetoothCodecConfig bcc1 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -235,7 +220,7 @@
                                      1000, 2000, 3000, 4000);
 
         BluetoothCodecConfig bcc2_same =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -244,7 +229,7 @@
         assertTrue(bcc1.equals(bcc2_same));
 
         BluetoothCodecConfig bcc3_codec_type =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -253,7 +238,7 @@
         assertFalse(bcc1.equals(bcc3_codec_type));
 
         BluetoothCodecConfig bcc4_codec_priority =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -262,7 +247,7 @@
         assertFalse(bcc1.equals(bcc4_codec_priority));
 
         BluetoothCodecConfig bcc5_sample_rate =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_48000,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -271,7 +256,7 @@
         assertFalse(bcc1.equals(bcc5_sample_rate));
 
         BluetoothCodecConfig bcc6_bits_per_sample =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -280,7 +265,7 @@
         assertFalse(bcc1.equals(bcc6_bits_per_sample));
 
         BluetoothCodecConfig bcc7_channel_mode =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -289,7 +274,7 @@
         assertFalse(bcc1.equals(bcc7_channel_mode));
 
         BluetoothCodecConfig bcc8_codec_specific1 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -298,7 +283,7 @@
         assertFalse(bcc1.equals(bcc8_codec_specific1));
 
         BluetoothCodecConfig bcc9_codec_specific2 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -307,7 +292,7 @@
         assertFalse(bcc1.equals(bcc9_codec_specific2));
 
         BluetoothCodecConfig bcc10_codec_specific3 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -316,7 +301,7 @@
         assertFalse(bcc1.equals(bcc10_codec_specific3));
 
         BluetoothCodecConfig bcc11_codec_specific4 =
-            new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                      BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                      BluetoothCodecConfig.SAMPLE_RATE_44100,
                                      BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -324,4 +309,21 @@
                                      1000, 2000, 3000, 4004);
         assertFalse(bcc1.equals(bcc11_codec_specific4));
     }
+
+    private BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType,
+            int codecPriority, int sampleRate, int bitsPerSample, int channelMode,
+            long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) {
+        return new BluetoothCodecConfig.Builder()
+                    .setCodecType(sourceCodecType)
+                    .setCodecPriority(codecPriority)
+                    .setSampleRate(sampleRate)
+                    .setBitsPerSample(bitsPerSample)
+                    .setChannelMode(channelMode)
+                    .setCodecSpecific1(codecSpecific1)
+                    .setCodecSpecific2(codecSpecific2)
+                    .setCodecSpecific3(codecSpecific3)
+                    .setCodecSpecific4(codecSpecific4)
+                    .build();
+
+    }
 }
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
index 83bf2ed..1cb2dca 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothCodecStatusTest.java
@@ -17,13 +17,13 @@
 package android.bluetooth;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.Objects;
 
 import junit.framework.TestCase;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
 /**
  * Unit test cases for {@link BluetoothCodecStatus}.
  * <p>
@@ -34,7 +34,7 @@
 
     // Codec configs: A and B are same; C is different
     private static final BluetoothCodecConfig config_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -42,7 +42,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig config_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -50,7 +50,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig config_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -59,7 +59,7 @@
 
     // Local capabilities: A and B are same; C is different
     private static final BluetoothCodecConfig local_capability1_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -69,7 +69,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability1_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -79,7 +79,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability1_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -89,7 +89,7 @@
 
 
     private static final BluetoothCodecConfig local_capability2_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -99,7 +99,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability2_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -109,7 +109,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability2_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -118,7 +118,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability3_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -128,7 +128,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability3_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -138,7 +138,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability3_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -147,7 +147,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability4_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -157,7 +157,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability4_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -167,7 +167,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability4_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000,
@@ -176,7 +176,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability5_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -190,7 +190,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability5_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -204,7 +204,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig local_capability5_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -219,7 +219,7 @@
 
     // Selectable capabilities: A and B are same; C is different
     private static final BluetoothCodecConfig selectable_capability1_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -228,7 +228,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability1_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -237,7 +237,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability1_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -245,7 +245,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability2_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -254,7 +254,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability2_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -263,7 +263,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability2_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -271,7 +271,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability3_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -280,7 +280,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability3_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -289,7 +289,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability3_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_16,
@@ -297,7 +297,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability4_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -306,7 +306,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability4_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -315,7 +315,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability4_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100,
                                  BluetoothCodecConfig.BITS_PER_SAMPLE_24,
@@ -323,7 +323,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability5_A =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -337,7 +337,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability5_B =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -351,7 +351,7 @@
                                  1000, 2000, 3000, 4000);
 
     private static final BluetoothCodecConfig selectable_capability5_C =
-        new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC,
                                  BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
                                  BluetoothCodecConfig.SAMPLE_RATE_44100 |
                                  BluetoothCodecConfig.SAMPLE_RATE_48000 |
@@ -363,79 +363,87 @@
                                  BluetoothCodecConfig.CHANNEL_MODE_STEREO,
                                  1000, 2000, 3000, 4000);
 
-    private static final BluetoothCodecConfig[] local_capability_A = {
-        local_capability1_A,
-        local_capability2_A,
-        local_capability3_A,
-        local_capability4_A,
-        local_capability5_A,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_A =
+            new ArrayList() {{
+                    add(local_capability1_A);
+                    add(local_capability2_A);
+                    add(local_capability3_A);
+                    add(local_capability4_A);
+                    add(local_capability5_A);
+            }};
 
-    private static final BluetoothCodecConfig[] local_capability_B = {
-        local_capability1_B,
-        local_capability2_B,
-        local_capability3_B,
-        local_capability4_B,
-        local_capability5_B,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B =
+            new ArrayList() {{
+                    add(local_capability1_B);
+                    add(local_capability2_B);
+                    add(local_capability3_B);
+                    add(local_capability4_B);
+                    add(local_capability5_B);
+            }};
 
-    private static final BluetoothCodecConfig[] local_capability_B_reordered = {
-        local_capability5_B,
-        local_capability4_B,
-        local_capability2_B,
-        local_capability3_B,
-        local_capability1_B,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B_REORDERED =
+            new ArrayList() {{
+                    add(local_capability5_B);
+                    add(local_capability4_B);
+                    add(local_capability2_B);
+                    add(local_capability3_B);
+                    add(local_capability1_B);
+            }};
 
-    private static final BluetoothCodecConfig[] local_capability_C = {
-        local_capability1_C,
-        local_capability2_C,
-        local_capability3_C,
-        local_capability4_C,
-        local_capability5_C,
-    };
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_C =
+            new ArrayList() {{
+                    add(local_capability1_C);
+                    add(local_capability2_C);
+                    add(local_capability3_C);
+                    add(local_capability4_C);
+                    add(local_capability5_C);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_A = {
-        selectable_capability1_A,
-        selectable_capability2_A,
-        selectable_capability3_A,
-        selectable_capability4_A,
-        selectable_capability5_A,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_A =
+            new ArrayList() {{
+                    add(selectable_capability1_A);
+                    add(selectable_capability2_A);
+                    add(selectable_capability3_A);
+                    add(selectable_capability4_A);
+                    add(selectable_capability5_A);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_B = {
-        selectable_capability1_B,
-        selectable_capability2_B,
-        selectable_capability3_B,
-        selectable_capability4_B,
-        selectable_capability5_B,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B =
+            new ArrayList() {{
+                    add(selectable_capability1_B);
+                    add(selectable_capability2_B);
+                    add(selectable_capability3_B);
+                    add(selectable_capability4_B);
+                    add(selectable_capability5_B);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_B_reordered = {
-        selectable_capability5_B,
-        selectable_capability4_B,
-        selectable_capability2_B,
-        selectable_capability3_B,
-        selectable_capability1_B,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B_REORDERED =
+            new ArrayList() {{
+                    add(selectable_capability5_B);
+                    add(selectable_capability4_B);
+                    add(selectable_capability2_B);
+                    add(selectable_capability3_B);
+                    add(selectable_capability1_B);
+            }};
 
-    private static final BluetoothCodecConfig[] selectable_capability_C = {
-        selectable_capability1_C,
-        selectable_capability2_C,
-        selectable_capability3_C,
-        selectable_capability4_C,
-        selectable_capability5_C,
-    };
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_C =
+            new ArrayList() {{
+                    add(selectable_capability1_C);
+                    add(selectable_capability2_C);
+                    add(selectable_capability3_C);
+                    add(selectable_capability4_C);
+                    add(selectable_capability5_C);
+            }};
 
     private static final BluetoothCodecStatus bcs_A =
-        new BluetoothCodecStatus(config_A, local_capability_A, selectable_capability_A);
+            new BluetoothCodecStatus(config_A, LOCAL_CAPABILITY_A, SELECTABLE_CAPABILITY_A);
     private static final BluetoothCodecStatus bcs_B =
-        new BluetoothCodecStatus(config_B, local_capability_B, selectable_capability_B);
+            new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B, SELECTABLE_CAPABILITY_B);
     private static final BluetoothCodecStatus bcs_B_reordered =
-        new BluetoothCodecStatus(config_B, local_capability_B_reordered,
-                                 selectable_capability_B_reordered);
+            new BluetoothCodecStatus(config_B, LOCAL_CAPABILITY_B_REORDERED,
+                                 SELECTABLE_CAPABILITY_B_REORDERED);
     private static final BluetoothCodecStatus bcs_C =
-        new BluetoothCodecStatus(config_C, local_capability_C, selectable_capability_C);
+            new BluetoothCodecStatus(config_C, LOCAL_CAPABILITY_C, SELECTABLE_CAPABILITY_C);
 
     @SmallTest
     public void testBluetoothCodecStatus_get_methods() {
@@ -444,16 +452,16 @@
         assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B));
         assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C));
 
-        assertTrue(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_A));
-        assertTrue(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_B));
-        assertFalse(Arrays.equals(bcs_A.getCodecsLocalCapabilities(), local_capability_C));
+        assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A));
+        assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B));
+        assertFalse(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C));
 
-        assertTrue(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
-                                 selectable_capability_A));
-        assertTrue(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
-                                  selectable_capability_B));
-        assertFalse(Arrays.equals(bcs_A.getCodecsSelectableCapabilities(),
-                                  selectable_capability_C));
+        assertTrue(bcs_A.getCodecsSelectableCapabilities()
+                                 .equals(SELECTABLE_CAPABILITY_A));
+        assertTrue(bcs_A.getCodecsSelectableCapabilities()
+                                  .equals(SELECTABLE_CAPABILITY_B));
+        assertFalse(bcs_A.getCodecsSelectableCapabilities()
+                                  .equals(SELECTABLE_CAPABILITY_C));
     }
 
     @SmallTest
@@ -465,4 +473,21 @@
         assertFalse(bcs_A.equals(bcs_C));
         assertFalse(bcs_C.equals(bcs_A));
     }
+
+    private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType,
+            int codecPriority, int sampleRate, int bitsPerSample, int channelMode,
+            long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) {
+        return new BluetoothCodecConfig.Builder()
+                    .setCodecType(sourceCodecType)
+                    .setCodecPriority(codecPriority)
+                    .setSampleRate(sampleRate)
+                    .setBitsPerSample(bitsPerSample)
+                    .setChannelMode(channelMode)
+                    .setCodecSpecific1(codecSpecific1)
+                    .setCodecSpecific2(codecSpecific2)
+                    .setCodecSpecific3(codecSpecific3)
+                    .setCodecSpecific4(codecSpecific4)
+                    .build();
+
+    }
 }
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
index 6471492..c3d707c 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
@@ -45,7 +45,6 @@
                 assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName());
             }
 
-            assertEquals(1, leAudioCodecConfig.getMaxCodecType());
             assertEquals(codecType, leAudioCodecConfig.getCodecType());
         }
     }
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index bcd794e..32d72b37 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -23,6 +23,7 @@
     ],
 
     aidl: {
+        generate_get_transaction_name: true,
         local_include_dirs: ["aidl"],
     },
 
@@ -46,6 +47,7 @@
         "androidx.test.ext.junit",
         "androidx.test.runner",
         "androidx.test.rules",
+        "kotlin-test",
         "mockito-target-minus-junit4",
         "ub-uiautomator",
         "platform-test-annotations",
diff --git a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
index 0456029..98485c0 100644
--- a/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
+++ b/core/tests/coretests/src/android/content/pm/ConstrainDisplayApisConfigTest.java
@@ -18,8 +18,7 @@
 
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.annotation.Nullable;
 import android.platform.test.annotations.Presubmit;
@@ -146,24 +145,17 @@
 
     private static void testNeverConstrainDisplayApis(String packageName, long version,
             boolean expected) {
-        boolean result = ConstrainDisplayApisConfig.neverConstrainDisplayApis(
-                buildApplicationInfo(packageName, version));
-        if (expected) {
-            assertTrue(result);
-        } else {
-            assertFalse(result);
-        }
+        ConstrainDisplayApisConfig config = new ConstrainDisplayApisConfig();
+        assertEquals(expected,
+                config.getNeverConstrainDisplayApis(buildApplicationInfo(packageName, version)));
     }
 
     private static void testAlwaysConstrainDisplayApis(String packageName, long version,
             boolean expected) {
-        boolean result = ConstrainDisplayApisConfig.alwaysConstrainDisplayApis(
-                buildApplicationInfo(packageName, version));
-        if (expected) {
-            assertTrue(result);
-        } else {
-            assertFalse(result);
-        }
+        ConstrainDisplayApisConfig config = new ConstrainDisplayApisConfig();
+
+        assertEquals(expected,
+                config.getAlwaysConstrainDisplayApis(buildApplicationInfo(packageName, version)));
     }
 
     private static ApplicationInfo buildApplicationInfo(String packageName, long version) {
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java
new file mode 100644
index 0000000..37cc9b7
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualKeyEventTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import android.view.KeyEvent;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualKeyEventTest {
+
+    @Test
+    public void keyEvent_emptyBuilder() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualKeyEvent.Builder().build());
+    }
+
+    @Test
+    public void keyEvent_noKeyCode() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new VirtualKeyEvent.Builder().setAction(VirtualKeyEvent.ACTION_DOWN).build());
+    }
+
+    @Test
+    public void keyEvent_noAction() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new VirtualKeyEvent.Builder().setKeyCode(KeyEvent.KEYCODE_A).build());
+    }
+
+    @Test
+    public void keyEvent_created() {
+        final VirtualKeyEvent event = new VirtualKeyEvent.Builder()
+                .setAction(VirtualKeyEvent.ACTION_DOWN)
+                .setKeyCode(KeyEvent.KEYCODE_A).build();
+        assertWithMessage("Incorrect key code").that(event.getKeyCode()).isEqualTo(
+                KeyEvent.KEYCODE_A);
+        assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+                VirtualKeyEvent.ACTION_DOWN);
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java
new file mode 100644
index 0000000..789e0bb
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseButtonEventTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualMouseButtonEventTest {
+
+    @Test
+    public void buttonEvent_emptyBuilder() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new VirtualMouseButtonEvent.Builder().build());
+    }
+
+    @Test
+    public void buttonEvent_noButtonCode() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualMouseButtonEvent.Builder()
+                .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE).build());
+    }
+
+    @Test
+    public void buttonEvent_noAction() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualMouseButtonEvent.Builder()
+                .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK).build());
+    }
+
+    @Test
+    public void buttonEvent_created() {
+        final VirtualMouseButtonEvent event = new VirtualMouseButtonEvent.Builder()
+                .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
+                .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK).build();
+        assertWithMessage("Incorrect button code").that(event.getButtonCode()).isEqualTo(
+                VirtualMouseButtonEvent.BUTTON_BACK);
+        assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+                VirtualMouseButtonEvent.ACTION_BUTTON_PRESS);
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java
new file mode 100644
index 0000000..c050816
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseRelativeEventTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualMouseRelativeEventTest {
+
+    @Test
+    public void relativeEvent_created() {
+        final VirtualMouseRelativeEvent event = new VirtualMouseRelativeEvent.Builder()
+                .setRelativeX(-5f)
+                .setRelativeY(8f).build();
+        assertWithMessage("Incorrect x value").that(event.getRelativeX()).isEqualTo(-5f);
+        assertWithMessage("Incorrect y value").that(event.getRelativeY()).isEqualTo(8f);
+    }
+}
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java
new file mode 100644
index 0000000..2259c74
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualMouseScrollEventTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualMouseScrollEventTest {
+
+    @Test
+    public void scrollEvent_xOutOfRange() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualMouseScrollEvent.Builder()
+                .setXAxisMovement(1.5f)
+                .setYAxisMovement(1.0f));
+    }
+
+    @Test
+    public void scrollEvent_yOutOfRange() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualMouseScrollEvent.Builder()
+                .setXAxisMovement(0.5f)
+                .setYAxisMovement(1.1f));
+    }
+
+    @Test
+    public void scrollEvent_created() {
+        final VirtualMouseScrollEvent event = new VirtualMouseScrollEvent.Builder()
+                .setXAxisMovement(-1f)
+                .setYAxisMovement(1f).build();
+        assertWithMessage("Incorrect x value").that(event.getXAxisMovement()).isEqualTo(-1f);
+        assertWithMessage("Incorrect y value").that(event.getYAxisMovement()).isEqualTo(1f);
+    }
+}
+
diff --git a/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
new file mode 100644
index 0000000..3f504a0
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/input/VirtualTouchEventTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.input;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.testng.Assert.assertThrows;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualTouchEventTest {
+
+    @Test
+    public void touchEvent_emptyBuilder() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder().build());
+    }
+
+    @Test
+    public void touchEvent_noAction() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .build());
+    }
+
+    @Test
+    public void touchEvent_noPointerId() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_DOWN)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setX(0f)
+                .setY(1f)
+                .build());
+    }
+
+    @Test
+    public void touchEvent_noToolType() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_DOWN)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .build());
+    }
+
+    @Test
+    public void touchEvent_noX() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_DOWN)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setY(1f)
+                .setPointerId(1)
+                .build());
+    }
+
+
+    @Test
+    public void touchEvent_noY() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_DOWN)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setX(0f)
+                .setPointerId(1)
+                .build());
+    }
+
+    @Test
+    public void touchEvent_created() {
+        final VirtualTouchEvent event = new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_DOWN)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .build();
+        assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+                VirtualTouchEvent.ACTION_DOWN);
+        assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo(
+                VirtualTouchEvent.TOOL_TYPE_FINGER);
+        assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f);
+        assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f);
+        assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1);
+    }
+
+    @Test
+    public void touchEvent_created_withPressureAndAxis() {
+        final VirtualTouchEvent event = new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_DOWN)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .setPressure(0.5f)
+                .setMajorAxisSize(10f)
+                .build();
+        assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+                VirtualTouchEvent.ACTION_DOWN);
+        assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo(
+                VirtualTouchEvent.TOOL_TYPE_FINGER);
+        assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f);
+        assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f);
+        assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1);
+        assertWithMessage("Incorrect pressure").that(event.getPressure()).isEqualTo(0.5f);
+        assertWithMessage("Incorrect major axis size").that(event.getMajorAxisSize()).isEqualTo(
+                10f);
+    }
+
+    @Test
+    public void touchEvent_cancelUsedImproperly() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_CANCEL)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .build());
+    }
+
+    @Test
+    public void touchEvent_palmUsedImproperly() {
+        assertThrows(IllegalArgumentException.class, () -> new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_MOVE)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_PALM)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .build());
+    }
+
+    @Test
+    public void touchEvent_palmAndCancelUsedProperly() {
+        final VirtualTouchEvent event = new VirtualTouchEvent.Builder()
+                .setAction(VirtualTouchEvent.ACTION_CANCEL)
+                .setToolType(VirtualTouchEvent.TOOL_TYPE_PALM)
+                .setX(0f)
+                .setY(1f)
+                .setPointerId(1)
+                .setPressure(0.5f)
+                .setMajorAxisSize(10f)
+                .build();
+        assertWithMessage("Incorrect action").that(event.getAction()).isEqualTo(
+                VirtualTouchEvent.ACTION_CANCEL);
+        assertWithMessage("Incorrect tool type").that(event.getToolType()).isEqualTo(
+                VirtualTouchEvent.TOOL_TYPE_PALM);
+        assertWithMessage("Incorrect x").that(event.getX()).isEqualTo(0f);
+        assertWithMessage("Incorrect y").that(event.getY()).isEqualTo(1f);
+        assertWithMessage("Incorrect pointer id").that(event.getPointerId()).isEqualTo(1);
+        assertWithMessage("Incorrect pressure").that(event.getPressure()).isEqualTo(0.5f);
+        assertWithMessage("Incorrect major axis size").that(event.getMajorAxisSize()).isEqualTo(
+                10f);
+    }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkPolicyTest.kt b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
new file mode 100644
index 0000000..d936cad
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkPolicyTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.text.format.Time.TIMEZONE_UTC
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.ByteArrayInputStream
+import java.io.DataInputStream
+import java.time.ZoneId
+import kotlin.test.assertEquals
+
+private const val TEST_IMSI1 = "TESTIMSI1"
+private const val TEST_SSID1 = "TESTISSID1"
+
+@RunWith(AndroidJUnit4::class)
+class NetworkPolicyTest {
+    @Test
+    fun testTemplateBackupRestore() {
+        assertPolicyBackupRestore(createTestPolicyForTemplate(
+                NetworkTemplate.buildTemplateWifi(TEST_SSID1)))
+        assertPolicyBackupRestore(createTestPolicyForTemplate(
+                NetworkTemplate.buildTemplateMobileAll(TEST_IMSI1)))
+        assertPolicyBackupRestore(createTestPolicyForTemplate(
+                NetworkTemplate.buildTemplateCarrierMetered(TEST_IMSI1)))
+    }
+
+    private fun createTestPolicyForTemplate(template: NetworkTemplate): NetworkPolicy {
+        return NetworkPolicy(template, NetworkPolicy.buildRule(5, ZoneId.of(TIMEZONE_UTC)),
+                NetworkPolicy.WARNING_DISABLED, NetworkPolicy.LIMIT_DISABLED,
+                NetworkPolicy.SNOOZE_NEVER, NetworkPolicy.SNOOZE_NEVER, NetworkPolicy.SNOOZE_NEVER,
+                /*metered*/ false, /*inferred*/ true)
+    }
+
+    private fun assertPolicyBackupRestore(policy: NetworkPolicy) {
+        val bytes = policy.bytesForBackup
+        val stream = DataInputStream(ByteArrayInputStream(bytes))
+        val restored = NetworkPolicy.getNetworkPolicyFromBackup(stream)
+        assertEquals(policy, restored)
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/AidlTest.java b/core/tests/coretests/src/android/os/AidlTest.java
index 4c51415..5f54b09 100644
--- a/core/tests/coretests/src/android/os/AidlTest.java
+++ b/core/tests/coretests/src/android/os/AidlTest.java
@@ -27,11 +27,12 @@
 public class AidlTest extends TestCase {
 
     private IAidlTest mRemote;
+    private AidlObject mLocal;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        AidlObject mLocal = new AidlObject();
+        mLocal = new AidlObject();
         mRemote = IAidlTest.Stub.asInterface(mLocal);
     }
 
@@ -417,5 +418,24 @@
         }
         assertEquals(good, true);
     }
+
+    @SmallTest
+    public void testGetTransactionName() throws Exception {
+        assertEquals(15, mLocal.getMaxTransactionId());
+
+        assertEquals("booleanArray",
+                mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_booleanArray));
+        assertEquals("voidSecurityException",
+                mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_voidSecurityException));
+        assertEquals("parcelableIn",
+                mLocal.getTransactionName(IAidlTest.Stub.TRANSACTION_parcelableIn));
+
+        assertEquals("IAidlTest:booleanArray",
+                mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_booleanArray));
+        assertEquals("IAidlTest:voidSecurityException",
+                mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_voidSecurityException));
+        assertEquals("IAidlTest:parcelableIn",
+                mLocal.getTransactionTraceName(IAidlTest.Stub.TRANSACTION_parcelableIn));
+    }
 }
 
diff --git a/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java b/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java
index 16dcff5..7ccbb01 100644
--- a/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java
+++ b/core/tests/coretests/src/android/os/storage/StorageManagerBaseTest.java
@@ -16,6 +16,10 @@
 
 package android.os.storage;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.test.InstrumentationTestCase;
@@ -23,6 +27,10 @@
 
 import libcore.io.Streams;
 
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
 import java.io.BufferedReader;
 import java.io.DataInputStream;
 import java.io.File;
@@ -39,6 +47,7 @@
 
     protected Context mContext = null;
     protected StorageManager mSm = null;
+    @Mock private File mFile;
     private static String LOG_TAG = "StorageManagerBaseTest";
     protected static final long MAX_WAIT_TIME = 120*1000;
     protected static final long WAIT_TIME_INCR = 5*1000;
@@ -121,12 +130,50 @@
      */
     @Override
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mContext = getInstrumentation().getContext();
         mSm = (StorageManager)mContext.getSystemService(android.content.Context.STORAGE_SERVICE);
 
     }
 
     /**
+     * Tests the space reserved for cache when system has high free space i.e. more than
+     * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total space.
+     */
+    @Test
+    public void testGetStorageCacheBytesUnderHighStorage() throws Exception {
+        when(mFile.getUsableSpace()).thenReturn(10000L);
+        when(mFile.getTotalSpace()).thenReturn(15000L);
+        long result = mSm.getStorageCacheBytes(mFile, 0);
+        assertThat(result).isEqualTo(1500L);
+    }
+
+    /**
+     * Tests the space reserved for cache when system has low free space i.e. less than
+     * StorageManager.STORAGE_THRESHOLD_PERCENT_LOW of total space.
+     */
+    @Test
+    public void testGetStorageCacheBytesUnderLowStorage() throws Exception {
+        when(mFile.getUsableSpace()).thenReturn(10000L);
+        when(mFile.getTotalSpace()).thenReturn(250000L);
+        long result = mSm.getStorageCacheBytes(mFile, 0);
+        assertThat(result).isEqualTo(5000L);
+    }
+
+    /**
+     * Tests the space reserved for cache when system has moderate free space i.e.more than
+     * StorageManager.STORAGE_THRESHOLD_PERCENT_LOW of total space but less than
+     * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total space.
+     */
+    @Test
+    public void testGetStorageCacheBytesUnderModerateStorage() throws Exception {
+        when(mFile.getUsableSpace()).thenReturn(10000L);
+        when(mFile.getTotalSpace()).thenReturn(100000L);
+        long result = mSm.getStorageCacheBytes(mFile, 0);
+        assertThat(result).isEqualTo(4666L);
+    }
+
+    /**
      * Creates an OBB file (with the given name), into the app's standard files directory
      *
      * @param name The name of the OBB file we want to create/write to
@@ -526,4 +573,4 @@
         doValidateIntContents(path + File.separator + "subdir2" + File.separator + "subdir2a"
                 + File.separator + "subdir2a1", "OneToOneThousandInts", 0, 1000);
     }
-}
\ No newline at end of file
+}
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 6be9306..bd4d80d 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -169,13 +169,13 @@
 
         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 – 22/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 – 22/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 – 9/2/2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
                         FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
 
@@ -251,35 +251,35 @@
 
         assertEquals("19–22 de enero de 2009",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
-        assertEquals("19–22 de ene. de 2009",
+        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 – jue, 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 – jueves, 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 – 22 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 – 22 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 – mié, 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 – 9 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 – feb 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 – 9 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 – jueves, 9 de febrero de 2012",
                 formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
 
         // The same tests but for es_ES.
@@ -291,10 +291,10 @@
         assertEquals("lun, 19 ene – jue, 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 – jueves, 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 – 22 de abril de 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
         assertEquals("19 ene – 22 abr 2009",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
@@ -311,9 +311,9 @@
         assertEquals("ene 2009 – feb 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 – 9 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 – jueves, 9 de febrero de 2012",
                 formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
     }
 
diff --git a/core/tests/coretests/src/android/view/MotionEventTest.java b/core/tests/coretests/src/android/view/MotionEventTest.java
index 78a8f7b..c4c983d 100644
--- a/core/tests/coretests/src/android/view/MotionEventTest.java
+++ b/core/tests/coretests/src/android/view/MotionEventTest.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_POINTER_DOWN;
 import static android.view.MotionEvent.TOOL_TYPE_FINGER;
@@ -214,4 +215,27 @@
         rotInvalid.transform(mat);
         assertEquals(-1, rotInvalid.getSurfaceRotation());
     }
+
+    @Test
+    public void testUsesPointerSourceByDefault() {
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
+                ACTION_DOWN, 0 /* x */, 0 /* y */, 0 /* metaState */);
+        assertTrue(event.isFromSource(SOURCE_CLASS_POINTER));
+    }
+
+    @Test
+    public void testLocationOffsetOnlyAppliedToNonPointerSources() {
+        final MotionEvent event = MotionEvent.obtain(0 /* downTime */, 0 /* eventTime */,
+                ACTION_DOWN, 10 /* x */, 20 /* y */, 0 /* metaState */);
+        event.offsetLocation(40, 50);
+
+        // The offset should be applied since a pointer source is used by default.
+        assertEquals(50, (int) event.getX());
+        assertEquals(70, (int) event.getY());
+
+        // The offset should not be applied if the source is changed to a non-pointer source.
+        event.setSource(InputDevice.SOURCE_JOYSTICK);
+        assertEquals(10, (int) event.getX());
+        assertEquals(20, (int) event.getY());
+    }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index c241e36..ad1f298 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -16,8 +16,11 @@
 
 package android.view.accessibility;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.accessibilityservice.MagnificationConfig;
+import android.annotation.NonNull;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Region;
 import android.os.Bundle;
@@ -97,6 +100,10 @@
 
     public void setOnKeyEventResult(boolean handled, int sequence) {}
 
+    public MagnificationConfig getMagnificationConfig(int displayId) {
+        return null;
+    }
+
     public float getMagnificationScale(int displayId) {
         return 0.0f;
     }
@@ -117,8 +124,8 @@
         return false;
     }
 
-    public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
-            float centerY, boolean animate) {
+    public boolean setMagnificationConfig(int displayId,
+            @NonNull MagnificationConfig config, boolean animate) {
         return false;
     }
 
@@ -138,6 +145,10 @@
         return false;
     }
 
+    public int setInputMethodEnabled(String imeId, boolean enabled) {
+        return AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
+    }
+
     public boolean isAccessibilityButtonAvailable() {
         return false;
     }
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 f8db0606..45504c0 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -132,6 +132,8 @@
         });
     }
 
+    private static final String TEST_MIME_TYPE = "application/TestType";
+
     private static final int CONTENT_PREVIEW_IMAGE = 1;
     private static final int CONTENT_PREVIEW_FILE = 2;
     private static final int CONTENT_PREVIEW_TEXT = 3;
@@ -269,7 +271,7 @@
         onView(withId(R.id.content_preview_thumbnail)).check(matches(isDisplayed()));
     }
 
-    @Test
+    @Test @Ignore
     public void twoOptionsAndUserSelectsOne() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -298,7 +300,7 @@
         assertThat(chosen[0], is(toChoose));
     }
 
-    @Test
+    @Test @Ignore
     public void fourOptionsStackedIntoOneTarget() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
 
@@ -351,7 +353,7 @@
         }
     }
 
-    @Test
+    @Test @Ignore
     public void updateChooserCountsAndModelAfterUserSelection() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -461,7 +463,7 @@
         assertThat(chosen[0], is(toChoose));
     }
 
-    @Test
+    @Test @Ignore
     public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -500,7 +502,7 @@
         assertThat(chosen[0], is(toChoose));
     }
 
-    @Test
+    @Test @Ignore
     public void hasLastChosenActivityAndOtherProfile() throws Exception {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -564,7 +566,7 @@
         assertEquals(mActivityRule.getActivityResult().getResultCode(), RESULT_OK);
     }
 
-    @Test @Ignore
+    @Test
     public void copyTextToClipboardLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -585,17 +587,15 @@
         onView(withId(R.id.chooser_copy_button)).perform(click());
 
         verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture());
-        // First is  Activity shown, Second is "with preview"
-        assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
+
+        // The last captured event is the selection of the target.
+        assertThat(logMakerCaptor.getValue().getCategory(),
                 is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET));
-        assertThat(logMakerCaptor
-                        .getAllValues().get(2)
-                        .getSubtype(),
-                is(1));
+        assertThat(logMakerCaptor.getValue().getSubtype(), is(1));
     }
 
 
-    @Test @Ignore
+    @Test
     public void testNearbyShareLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -614,12 +614,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -628,22 +628,37 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_NEARBY_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_NEARBY_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
 
 
-    @Test @Ignore
+    @Test
     public void testEditImageLogs() throws Exception {
         Intent sendIntent = createSendImageIntent(
                 Uri.parse("android.resource://com.android.frameworks.coretests/"
@@ -668,11 +683,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("image/png"));
@@ -681,17 +697,32 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(1));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_EDIT_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_EDIT_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
 
@@ -778,7 +809,7 @@
     @Test
     public void testOnCreateLogging() {
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         MetricsLogger mockLogger = sOverrides.metricsLogger;
         ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
@@ -794,7 +825,7 @@
         assertThat(logMakerCaptor
                 .getAllValues().get(0)
                 .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE),
-                is("TestType"));
+                is(TEST_MIME_TYPE));
         assertThat(logMakerCaptor
                         .getAllValues().get(0)
                         .getSubtype(),
@@ -804,7 +835,7 @@
     @Test
     public void testOnCreateLoggingFromWorkProfile() {
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         sOverrides.alternateProfileSetting = MetricsEvent.MANAGED_PROFILE;
         MetricsLogger mockLogger = sOverrides.metricsLogger;
         ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
@@ -820,7 +851,7 @@
         assertThat(logMakerCaptor
                         .getAllValues().get(0)
                         .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE),
-                is("TestType"));
+                is(TEST_MIME_TYPE));
         assertThat(logMakerCaptor
                         .getAllValues().get(0)
                         .getSubtype(),
@@ -1476,7 +1507,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         markWorkProfileUserAvailable();
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1490,7 +1521,7 @@
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -1512,7 +1543,7 @@
                 workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         markWorkProfileUserAvailable();
 
         final ChooserWrapperActivity activity =
@@ -1538,7 +1569,7 @@
                 createResolvedComponentsForTest(workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1549,7 +1580,7 @@
         assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
     }
 
-    @Test
+    @Test @Ignore
     public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
         // enable the work tab feature flag
         ResolverActivity.ENABLE_TABBED_VIEW = true;
@@ -1561,7 +1592,7 @@
                 createResolvedComponentsForTest(workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
             chosen[0] = targetInfo.getResolveInfo();
@@ -1597,7 +1628,7 @@
         sOverrides.hasCrossProfileIntents = false;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1623,7 +1654,7 @@
         sOverrides.isQuietModeEnabled = true;
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         ResolverActivity.ENABLE_TABBED_VIEW = true;
         final ChooserWrapperActivity activity =
@@ -1649,7 +1680,7 @@
                 createResolvedComponentsForTest(0);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1676,7 +1707,7 @@
         sOverrides.isQuietModeEnabled = true;
         sOverrides.hasCrossProfileIntents = false;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -1701,7 +1732,7 @@
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         sOverrides.isQuietModeEnabled = true;
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -1714,7 +1745,7 @@
                 .check(matches(isDisplayed()));
     }
 
-    @Test @Ignore
+    @Test
     public void testAppTargetLogging() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1743,12 +1774,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -1757,17 +1788,32 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_APP_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
     @Test @Ignore
@@ -1850,7 +1896,7 @@
                         .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
     }
 
-    @Test @Ignore
+    @Test
     public void testEmptyDirectRowLogging() throws InterruptedException {
         Intent sendIntent = createSendTextIntent();
         // We need app targets for direct targets to get displayed
@@ -1874,12 +1920,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -1888,20 +1934,30 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_EMPTY_DIRECT_SHARE_ROW event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(5).event.getId(),
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // SHARESHEET_EMPTY_DIRECT_SHARE_ROW:
+        assertThat(logger.event(4).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+
+        // Sixth is just an artifact of test set-up:
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        assertThat(logger.numCalls(), is(6));
     }
 
-    @Test @Ignore
+    @Test
     public void testCopyTextToClipboardLogging() throws Exception {
         Intent sendIntent = createSendTextIntent();
         List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
@@ -1920,12 +1976,12 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(6));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
         assertThat(logger.get(1).mimeType, is("text/plain"));
@@ -1934,17 +1990,32 @@
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth and fifth are just artifacts of test set-up
-        // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
-        assertThat(logger.get(5).targetType,
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth and sixth are just artifacts of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_EXPANDED.getId()));
+
+        // SHARESHEET_EDIT_TARGET_SELECTED:
+        assertThat(logger.get(6).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+        assertThat(logger.get(6).targetType,
                 is(ChooserActivityLogger
                         .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
+
+        // No more events.
+        assertThat(logger.numCalls(), is(7));
     }
 
     @Test
@@ -1959,7 +2030,7 @@
                 createResolvedComponentsForTest(workProfileTargets);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         final ChooserWrapperActivity activity =
                 mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
@@ -1971,42 +2042,65 @@
 
         ChooserActivityLoggerFake logger =
                 (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
-        assertThat(logger.numCalls(), is(8));
-        // first one should be SHARESHEET_TRIGGERED uievent
-        assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(0).event.getId(),
+
+        // SHARESHEET_TRIGGERED:
+        assertThat(logger.event(0).getId(),
                 is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
-        // second one should be SHARESHEET_STARTED event
+
+        // SHARESHEET_STARTED:
         assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
         assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
-        assertThat(logger.get(1).mimeType, is("TestType"));
+        assertThat(logger.get(1).mimeType, is(TEST_MIME_TYPE));
         assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
         assertThat(logger.get(1).appProvidedApp, is(0));
         assertThat(logger.get(1).appProvidedDirect, is(0));
         assertThat(logger.get(1).isWorkprofile, is(false));
         assertThat(logger.get(1).previewType, is(3));
-        // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(2).event.getId(),
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(2).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // fourth one is artifact of test setup
-        // fifth one is switch to work profile
-        assertThat(logger.get(4).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(4).event.getId(),
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(3).getId(),
                 is(ChooserActivityLogger
-                        .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
-        // sixth one should be SHARESHEET_APP_LOAD_COMPLETE uievent
-        assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(5).event.getId(),
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Fifth is just an artifact of test set-up:
+        assertThat(logger.event(4).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+
+        // SHARESHEET_PROFILE_CHANGED:
+        assertThat(logger.event(5).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent
+                        .SHARESHEET_PROFILE_CHANGED.getId()));
+
+        // Repeat the loading steps in the new profile:
+
+        // SHARESHEET_APP_LOAD_COMPLETE:
+        assertThat(logger.event(6).getId(),
                 is(ChooserActivityLogger
                         .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
-        // seventh one is artifact of test setup
-        // eigth one is switch to work profile
-        assertThat(logger.get(7).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
-        assertThat(logger.get(7).event.getId(),
+
+        // SHARESHEET_DIRECT_LOAD_COMPLETE:
+        assertThat(logger.event(7).getId(),
                 is(ChooserActivityLogger
-                        .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+                        .SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE.getId()));
+
+        // Ninth is again an artifact of test set-up:
+        assertThat(logger.event(8).getId(),
+                is(ChooserActivityLogger
+                        .SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW.getId()));
+
+        // SHARESHEET_PROFILE_CHANGED:
+        assertThat(logger.event(9).getId(),
+                is(ChooserActivityLogger.SharesheetStandardEvent
+                        .SHARESHEET_PROFILE_CHANGED.getId()));
+
+        // No more events (this profile was already loaded).
+        assertThat(logger.numCalls(), is(10));
     }
 
     @Test
@@ -2019,7 +2113,7 @@
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
             chosen[0] = targetInfo.getResolveInfo();
@@ -2043,7 +2137,7 @@
                 Mockito.isA(List.class)))
                 .thenReturn(new ArrayList<>(personalResolvedComponentInfos));
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         ResolveInfo[] chosen = new ResolveInfo[1];
         sOverrides.onSafelyStartCallback = targetInfo -> {
             chosen[0] = targetInfo.getResolveInfo();
@@ -2244,7 +2338,7 @@
             return null;
         };
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -2275,7 +2369,7 @@
             return null;
         };
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -2299,7 +2393,7 @@
                 createResolvedComponentsForTest(3);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         sOverrides.isWorkProfileUserRunning = false;
 
         final ChooserWrapperActivity activity =
@@ -2331,7 +2425,7 @@
             return null;
         };
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
 
         mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
         waitForIdle();
@@ -2355,7 +2449,7 @@
                 createResolvedComponentsForTest(3);
         setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
         Intent sendIntent = createSendTextIntent();
-        sendIntent.setType("TestType");
+        sendIntent.setType(TEST_MIME_TYPE);
         sOverrides.isWorkProfileUserUnlocked = false;
 
         final ChooserWrapperActivity activity =
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 8ec33bf..c1a45c4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -37,7 +37,6 @@
 import android.app.ActivityManager;
 import android.os.BatteryStats;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.view.Display;
 
 import androidx.test.filters.LargeTest;
@@ -53,6 +52,7 @@
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
 public class BatteryStatsImplTest {
     private static final long[] CPU_FREQS = {1, 2, 3, 4, 5};
     private static final int NUM_CPU_FREQS = CPU_FREQS.length;
@@ -62,29 +62,26 @@
     @Mock
     private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
+    private final MockClock mMockClock = new MockClock();
     private MockBatteryStatsImpl mBatteryStatsImpl;
 
-    private MockClock mMockClock = new MockClock();
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
         when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS);
         when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
         when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
         mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
                 .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
-                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
-                .setTrackingCpuByProcStateEnabled(true);
+                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
     }
 
     @Test
     public void testUpdateProcStateCpuTimes() {
         mBatteryStatsImpl.setOnBatteryInternal(true);
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         final int[] testUids = {10032, 10048, 10145, 10139};
         final int[] activityManagerProcStates = {
@@ -99,15 +96,24 @@
                 PROCESS_STATE_TOP,
                 PROCESS_STATE_CACHED
         };
-        addPendingUids(testUids, testProcStates);
 
         // Initialize time-in-freq counters
         mMockClock.realtime = 1000;
         for (int i = 0; i < testUids.length; ++i) {
-            mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
             mockKernelSingleUidTimeReader(testUids[i], new long[5]);
+            mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
         }
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
+
+        final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+
+        // Verify there are no cpu times initially.
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
+                assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
+            }
+        }
 
         // Obtain initial CPU time-in-freq counts
         final long[][] cpuTimes = {
@@ -117,24 +123,14 @@
                 {4859048, 348903, 4578967, 5973894, 298549}
         };
 
-        final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+        mMockClock.realtime += 1000;
 
         for (int i = 0; i < testUids.length; ++i) {
             mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
-
-            // Verify there are no cpu times initially.
-            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
-            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
-                assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
-                assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
-            }
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
         }
-        addPendingUids(testUids, testProcStates);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -155,19 +151,19 @@
                 {945894, 9089432, 19478, 3834, 7845},
                 {843895, 43948, 949582, 99, 384}
         };
+
+        mMockClock.realtime += 1000;
+
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
             for (int j = 0; j < cpuTimes[i].length; j++) {
                 newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j];
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
         }
-        addPendingUids(testUids, testProcStates);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -186,10 +182,8 @@
         }
 
         // Validate the on-battery-screen-off counter
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
-                    mMockClock.realtime * 1000);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+                mMockClock.realtime * 1000);
 
         final long[][] delta2 = {
                 {95932, 2943, 49834, 89034, 139},
@@ -197,19 +191,19 @@
                 {678, 7498, 9843, 889, 4894},
                 {488, 998, 8498, 394, 574}
         };
+
+        mMockClock.realtime += 1000;
+
         for (int i = 0; i < testUids.length; ++i) {
             long[] newCpuTimes = new long[cpuTimes[i].length];
             for (int j = 0; j < cpuTimes[i].length; j++) {
                 newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j];
             }
             mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
         }
-        addPendingUids(testUids, testProcStates);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
-
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -239,24 +233,25 @@
                 {3049509483598l, 4597834, 377654, 94589035, 7854},
                 {9493, 784, 99895, 8974893, 9879843}
         };
-        for (int i = 0; i < testUids.length; ++i) {
-            long[] newCpuTimes = new long[cpuTimes[i].length];
-            for (int j = 0; j < cpuTimes[i].length; j++) {
-                newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
-            }
-            mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
-        }
-        addPendingUids(testUids, testProcStates);
+
+        mMockClock.realtime += 1000;
+
         final int parentUid = testUids[1];
         final int childUid = 99099;
         addIsolatedUid(parentUid, childUid);
         final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
         mockKernelSingleUidTimeReader(childUid, isolatedUidCpuTimes, isolatedUidCpuTimes);
 
-        mMockClock.realtime += 1000;
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
+        for (int i = 0; i < testUids.length; ++i) {
+            long[] newCpuTimes = new long[cpuTimes[i].length];
+            for (int j = 0; j < cpuTimes[i].length; j++) {
+                newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
+            }
+            mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.realtime);
+        }
 
-        verifyNoPendingUids();
         for (int i = 0; i < testUids.length; ++i) {
             final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
             for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -284,11 +279,9 @@
     }
 
     @Test
-    public void testCopyFromAllUidsCpuTimes() {
+    public void testUpdateCpuTimesForAllUids() {
         mBatteryStatsImpl.setOnBatteryInternal(false);
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
         mMockClock.realtime = 1000;
 
@@ -299,14 +292,14 @@
                 PROCESS_STATE_TOP,
                 PROCESS_STATE_CACHED
         };
-        addPendingUids(testUids, testProcStates);
 
         for (int i = 0; i < testUids.length; ++i) {
             BatteryStatsImpl.Uid uid = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
             uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
             mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
+            mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+                    mMockClock.elapsedRealtime());
         }
-        mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
 
         final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
         long[][] allCpuTimes = {
@@ -330,9 +323,8 @@
         }
 
         mMockClock.realtime += 1000;
-        mBatteryStatsImpl.copyFromAllUidsCpuTimes(true, false);
 
-        verifyNoPendingUids();
+        mBatteryStatsImpl.updateCpuTimesForAllUids();
 
         final long[] timeInFreqs = new long[NUM_CPU_FREQS];
 
@@ -411,9 +403,7 @@
         final int releaseTimeMs = 1005;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -436,9 +426,7 @@
         final int acquireTimeMs = 1000;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -464,9 +452,7 @@
         final int releaseTimeMs_2 = 1009;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -496,9 +482,7 @@
         final int releaseTimeMs_2 = 1009;
         final int currentTimeMs = 1011;
 
-        synchronized (mBatteryStatsImpl) {
-            mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
-        }
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
 
         // Create a Uid Object
         final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -523,17 +507,4 @@
         final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
         u.addIsolatedUid(childUid);
     }
-
-    private void addPendingUids(int[] uids, int[] procStates) {
-        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
-        for (int i = 0; i < uids.length; ++i) {
-            pendingUids.put(uids[i], procStates[i]);
-        }
-    }
-
-    private void verifyNoPendingUids() {
-        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
-        assertEquals("There shouldn't be any pending uids left: " + pendingUids,
-                0, pendingUids.size());
-    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 4733f86..69e617a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -83,7 +83,7 @@
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isLessThan(5000);
+        assertThat(parcel.dataSize()).isLessThan(5500);
 
         parcel.setDataPosition(0);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 441e85d..9172d34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -26,9 +26,6 @@
 import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
 import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
 
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;
-
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
@@ -51,7 +48,6 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DebugUtils;
 import android.util.KeyValueListParser;
@@ -125,11 +121,6 @@
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
         executeCmd("cmd deviceidle whitelist +" + TEST_PKG);
-
-        final ArrayMap<String, String> desiredConstants = new ArrayMap<>();
-        desiredConstants.put(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, Boolean.toString(true));
-        desiredConstants.put(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS, Integer.toString(0));
-        updateBatteryStatsConstants(desiredConstants);
         checkCpuTimesAvailability();
     }
 
@@ -517,125 +508,6 @@
         batteryOffScreenOn();
     }
 
-    @Test
-    public void testCpuFreqTimes_trackingDisabled() throws Exception {
-        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
-            Log.w(TAG, "Skipping " + testName.getMethodName()
-                    + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
-                    + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
-            return;
-        }
-
-        final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
-                Settings.Global.BATTERY_STATS_CONSTANTS);
-        try {
-            batteryOnScreenOn();
-            forceStop();
-            resetBatteryStats();
-            final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-            assertNull("Initial snapshot should be null, initial="
-                    + Arrays.toString(initialSnapshot), initialSnapshot);
-            assertNull("Initial top state snapshot should be null",
-                    getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
-
-            doSomeWork(PROCESS_STATE_TOP);
-            forceStop();
-
-            final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            final String msgCpuTimes = getAllCpuTimesMsg();
-            assertCpuTimesValid(cpuTimesMs);
-            long actualCpuTimeMs = 0;
-            for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
-                actualCpuTimeMs += cpuTimesMs[i];
-            }
-            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
-                    WORK_DURATION_MS, actualCpuTimeMs);
-
-            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);
-
-            doSomeWork(PROCESS_STATE_TOP);
-            forceStop();
-
-            final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            assertCpuTimesValid(cpuTimesMs2);
-            assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
-                    "Unexpected cpu times with tracking off");
-
-            updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);
-
-            final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            assertCpuTimesValid(cpuTimesMs3);
-            assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 500,
-                    "Unexpected cpu times after turning on tracking");
-
-            doSomeWork(PROCESS_STATE_TOP);
-            forceStop();
-
-            final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
-            assertCpuTimesValid(cpuTimesMs4);
-            actualCpuTimeMs = 0;
-            for (int i = 0; i < cpuTimesMs4.length / 2; ++i) {
-                actualCpuTimeMs += cpuTimesMs4[i];
-            }
-            assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
-                    2 * WORK_DURATION_MS, actualCpuTimeMs);
-
-            batteryOffScreenOn();
-        } finally {
-            Settings.Global.putString(sContext.getContentResolver(),
-                    Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
-        }
-    }
-
-    private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
-        for (int i = actual.length - 1; i >= 0; --i) {
-            if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
-                fail(errMsg + ", actual=" + Arrays.toString(actual)
-                        + ", expected=" + Arrays.toString(expected) + ", delta=" + delta);
-            }
-        }
-    }
-
-    private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
-            throws Exception {
-        final String newConstants;
-        final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
-        if (originalConstants == null || "null".equals(originalConstants)) {
-            newConstants = setting;
-        } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
-            newConstants = originalConstants.replaceAll(
-                    KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
-        } else {
-            newConstants = originalConstants + "," + setting;
-        }
-        Settings.Global.putString(sContext.getContentResolver(),
-                Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
-        assertTrackPerProcStateCpuTimesSetting(enabled);
-    }
-
-    private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
-        final String expectedValue = Boolean.toString(enabled);
-        assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
-            final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
-            return expectedValue.equals(actualValue)
-                    ? null : "expected=" + expectedValue + ", actual=" + actualValue;
-        }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
-    }
-
-    private String getSettingValueFromDump(String key) throws Exception {
-        final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
-        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
-        splitter.setString(settingsDump);
-        String next;
-        while (splitter.hasNext()) {
-            next = splitter.next().trim();
-            if (next.startsWith(key)) {
-                return next.split("=")[1];
-            }
-        }
-        return null;
-    }
-
     private void assertCpuTimesValid(long[] cpuTimes) {
         assertNotNull(cpuTimes);
         for (int i = 0; i < cpuTimes.length; ++i) {
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 1ae30db..d5b0f0a 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -98,6 +98,8 @@
                 new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
         supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
 
+        when(mMockCpuUidFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
+
         mStatsRule.getBatteryStats()
                 .setUserInfoProvider(mMockUserInfoProvider)
                 .setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
@@ -277,8 +279,6 @@
 
     @Test
     public void testTimerBasedModel_byProcessState() {
-        mStatsRule.getBatteryStats().setTrackingCpuByProcStateEnabled(true);
-
         when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
 
         when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
@@ -311,7 +311,7 @@
         }).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
 
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
-        mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+        mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
         mockSingleUidTimeReader(APP_UID1, new long[]{1000, 2000, 3000, 4000});
         mockSingleUidTimeReader(APP_UID2, new long[]{1111, 2222, 3333, 4444});
@@ -326,7 +326,7 @@
         }).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
 
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
-        mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+        mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
         mockSingleUidTimeReader(APP_UID1, new long[] {5000, 6000, 7000, 8000});
         mockSingleUidTimeReader(APP_UID2, new long[]{5555, 6666, 7777, 8888});
@@ -346,7 +346,7 @@
         }).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
 
         mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
-        mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+        mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
 
         CpuPowerCalculator calculator =
                 new CpuPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 48a1da1..8d9d79d 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -26,6 +26,8 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
 import android.telephony.DataConnectionRealTimeInfo;
@@ -37,12 +39,15 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.collect.Range;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @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;
@@ -55,7 +60,7 @@
             .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})
+                    new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
             .initMeasuredEnergyStatsLocked();
 
     @Test
@@ -81,7 +86,7 @@
 
         // Note established network
         stats.noteNetworkInterfaceForTransports("cellular",
-                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR });
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
@@ -89,7 +94,7 @@
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
-                new int[] {100, 200, 300, 400, 500}, 600);
+                new int[]{100, 200, 300, 400, 500}, 600);
         stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000);
 
         mStatsRule.setTime(12_000_000, 12_000_000);
@@ -119,6 +124,90 @@
     }
 
     @Test
+    public void testTimerBasedModel_byProcessState() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
+
+        mStatsRule.setTime(1000, 1000);
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
+
+        // 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);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        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});
+
+        // Note application network activity
+        mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100));
+
+        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000);
+
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
+
+        mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 250, 2000, 80, 200));
+
+        stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000);
+
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC()).isAtMost(0);
+        // 12000-8000 = 4000 ms == 4_000_000 us
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(BatteryConsumer.PROCESS_STATE_ANY))
+                .isEqualTo(4_000_000);
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isEqualTo(3_000_000);
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(
+                BatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isEqualTo(1_000_000);
+        assertThat(uid.getMobileRadioActiveTimeInProcessState(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
+                .isEqualTo(0);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
+                .powerProfileModeledOnly()
+                .includePowerModels()
+                .includeProcessStateData()
+                .build(), calculator);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+
+        final BatteryConsumer.Key foreground = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key background = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key fgs = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        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);
+    }
+
+    @Test
     public void testMeasuredEnergyBasedModel() {
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
@@ -141,7 +230,7 @@
 
         // Note established network
         stats.noteNetworkInterfaceForTransports("cellular",
-                new int[] { NetworkCapabilities.TRANSPORT_CELLULAR });
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
@@ -149,7 +238,7 @@
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
-                new int[] {100, 200, 300, 400, 500}, 600);
+                new int[]{100, 200, 300, 400, 500}, 600);
         stats.noteModemControllerActivity(mai, 10_000_000, 10000, 10000);
 
         mStatsRule.setTime(12_000_000, 12_000_000);
@@ -177,4 +266,87 @@
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
+
+    @Test
+    public void testMeasuredEnergyBasedModel_byProcessState() {
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
+
+        mStatsRule.setTime(1000, 1000);
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_FOREGROUND, 1000);
+
+        // 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);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        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});
+
+        // Note application network activity
+        mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 100, 2000, 20, 100));
+
+        stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000);
+
+        uid.setProcessStateForTest(
+                BatteryStats.Uid.PROCESS_STATE_BACKGROUND, 11000);
+
+        mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
+                .insertEntry("cellular", APP_UID, 0, 0, 1000, 250, 2000, 80, 200));
+
+        stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000);
+
+        mStatsRule.setTime(20000, 20000);
+
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC())
+                .isIn(Range.open(20_000_000L, 21_000_000L));
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND))
+                .isIn(Range.open(13_000_000L, 14_000_000L));
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+                BatteryConsumer.PROCESS_STATE_BACKGROUND))
+                .isIn(Range.open(7_000_000L, 8_000_000L));
+        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
+                .isEqualTo(0);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
+                .includePowerModels()
+                .includeProcessStateData()
+                .build(), calculator);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+
+        final BatteryConsumer.Key foreground = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND);
+        final BatteryConsumer.Key background = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_BACKGROUND);
+        final BatteryConsumer.Key fgs = uidConsumer.getKey(
+                BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+        assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(3.62064);
+        assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(2.08130);
+        assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index d16689c..4faf349 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,10 +16,13 @@
 
 package com.android.internal.os;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 import android.net.NetworkStats;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.SparseIntArray;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -59,6 +62,9 @@
         // A no-op handler.
         mHandler = new Handler(Looper.getMainLooper()) {
         };
+
+        mCpuUidFreqTimeReader = mock(KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader.class);
+        when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200});
     }
 
     public void initMeasuredEnergyStats(String[] customBucketNames) {
@@ -178,15 +184,6 @@
         return this;
     }
 
-    public MockBatteryStatsImpl setTrackingCpuByProcStateEnabled(boolean enabled) {
-        mConstants.TRACK_CPU_TIMES_BY_PROC_STATE = enabled;
-        return this;
-    }
-
-    public SparseIntArray getPendingUids() {
-        return mPendingUids;
-    }
-
     public int getAndClearExternalStatsSyncFlags() {
         final int flags = mExternalStatsSync.flags;
         mExternalStatsSync.flags = 0;
@@ -217,18 +214,6 @@
         }
 
         @Override
-        public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery,
-                boolean onBatteryScreenOff, long delayMillis) {
-            return null;
-        }
-
-        @Override
-        public Future<?> scheduleCopyFromAllUidsCpuTimes(
-                boolean onBattery, boolean onBatteryScreenOff) {
-            return null;
-        }
-
-        @Override
         public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
                 boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
             flags |= flag;
@@ -248,6 +233,11 @@
         public Future<?> scheduleSyncDueToBatteryLevelChange(long delayMillis) {
             return null;
         }
+
+        @Override
+        public Future<?> scheduleSyncDueToProcessStateChange(long delayMillis) {
+            return null;
+        }
     }
 }
 
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
index dc5bc97..dbb2cf1 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
@@ -465,16 +465,23 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
+        final int[] supportedMultiStateBuckets = new int[]{POWER_BUCKET_SCREEN_ON};
         final MeasuredEnergyStats.Config config =
                 new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
-                        new int[0], new String[]{"s"});
+                        supportedMultiStateBuckets, new String[]{"s1", "s2"});
         final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
-        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
+        stats.setState(1, 0);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10, 1000);
+        stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5, 2000);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
         stats.updateCustomBucket(0, 50);
         stats.updateCustomBucket(1, 60);
 
+        assertThat(stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 0))
+                .isEqualTo(0);
+        assertThat(stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 1))
+                .isEqualTo(15);
+
         MeasuredEnergyStats.resetIfNotNull(stats);
         // All charges should be reset to 0
         for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
@@ -485,7 +492,13 @@
                 assertFalse(stats.isStandardBucketSupported(i));
                 assertEquals(POWER_DATA_UNAVAILABLE, stats.getAccumulatedStandardBucketCharge(i));
             }
+
         }
+        assertThat(stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 0))
+                .isEqualTo(0);
+        assertThat(stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 1))
+                .isEqualTo(0);
+
         for (int i = 0; i < customBucketNames.length; i++) {
             assertEquals(0, stats.getAccumulatedCustomBucketCharge(i));
         }
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 756425e..0b8dc3f 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -180,6 +180,7 @@
     <assign-permission name="android.permission.WATCH_APPOPS" uid="cameraserver" />
     <assign-permission name="android.permission.MANAGE_APP_OPS_MODES" uid="cameraserver" />
     <assign-permission name="android.permission.OBSERVE_SENSOR_PRIVACY" uid="cameraserver" />
+    <assign-permission name="android.permission.REAL_GET_TASKS" uid="cameraserver" />
 
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0a2670a..e61a665 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -154,6 +154,7 @@
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
         <permission name="android.permission.MODIFY_AUDIO_ROUTING" />
+        <permission name="android.permission.WRITE_SECURE_SETTINGS" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
@@ -393,6 +394,8 @@
         <!-- Permissions required for Incremental CTS tests -->
         <permission name="com.android.permission.USE_INSTALLER_V2"/>
         <permission name="android.permission.LOADER_USAGE_STATS"/>
+        <!-- Permissions required for Package Verifier tests -->
+        <permission name="android.permission.PACKAGE_VERIFICATION_AGENT" />
         <!-- Permission required to test system only camera devices. -->
         <permission name="android.permission.SYSTEM_CAMERA" />
         <!-- Permission required to test ExplicitHealthCheckServiceImpl. -->
@@ -424,7 +427,6 @@
         <permission name="android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS" />
         <permission name="android.permission.MANAGE_COMPANION_DEVICES" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING" />
-        <permission name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
         <permission name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
         <permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
         <!-- Permission required for testing registering pull atom callbacks. -->
@@ -494,6 +496,8 @@
         <!-- Permission required for CTS test - SystemMediaRouter2Test -->
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
         <permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
+        <!-- Permission required for CTS test - CallAudioInterceptionTest -->
+        <permission name="android.permission.CALL_AUDIO_INTERCEPTION"/>
         <!-- Permission required for CTS test - CtsPermission5TestCases -->
         <permission name="android.permission.RENOUNCE_PERMISSIONS" />
         <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
@@ -514,6 +518,11 @@
         <permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
         <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
         <permission name="android.permission.LOCK_DEVICE" />
+        <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+        <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+        <!-- Permission required for CTS test - CommunalManagerTest -->
+        <permission name="android.permission.WRITE_COMMUNAL_STATE" />
+        <permission name="android.permission.READ_COMMUNAL_STATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.statementservice">
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
index 9a41cb4..fa173072 100644
--- a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -189,7 +189,10 @@
         if (!actualPerm.containsAll(expectedPerm)) {
             return buildDescription(tree)
                     .setMessage("Method " + method.name.toString() + "() annotated " + expectedPerm
-                            + " but too wide; only invokes methods requiring " + actualPerm)
+                            + " but too wide; only invokes methods requiring " + actualPerm
+                            + "\n  If calling an AIDL interface, it can be annotated by adding:"
+                            + "\n  @JavaPassthrough(annotation=\""
+                            + "@android.annotation.RequiresPermission(...)\")")
                     .build();
         }
 
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 8844370..8d3eadb 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -27,8 +27,9 @@
     // Note: This field is accessed by native code.
     public long mNativeObject; // BLASTBufferQueue*
 
-    private static native long nativeCreate(String name, long surfaceControl, long width,
+    private static native long nativeCreateAndUpdate(String name, long surfaceControl, long width,
             long height, int format);
+    private static native long nativeCreate(String name);
     private static native void nativeDestroy(long ptr);
     private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
     private static native void nativeSetSyncTransaction(long ptr, long transactionPtr,
@@ -43,7 +44,11 @@
     /** Create a new connection with the surface flinger. */
     public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
             @PixelFormat.Format int format) {
-        mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
+        mNativeObject = nativeCreateAndUpdate(name, sc.mNativeObject, width, height, format);
+    }
+
+    public BLASTBufferQueue(String name) {
+        mNativeObject = nativeCreate(name);
     }
 
     public void destroy() {
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index a612265..425a378 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -67,7 +67,7 @@
      * @hide
      */
     protected int mDensity = Bitmap.DENSITY_NONE;
-    private boolean mAllowHwBitmapsInSwMode = false;
+    private boolean mAllowHwFeaturesInSwMode = false;
 
     protected void throwIfCannotDraw(Bitmap bitmap) {
         if (bitmap.isRecycled()) {
@@ -101,14 +101,14 @@
 
     public void drawArc(float left, float top, float right, float bottom, float startAngle,
             float sweepAngle, boolean useCenter, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawArc(mNativeCanvasWrapper, left, top, right, bottom, startAngle, sweepAngle,
                 useCenter, paint.getNativeInstance());
     }
 
     public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawArc(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, useCenter,
                 paint);
     }
@@ -119,14 +119,14 @@
 
     public void drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint) {
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawBitmap(mNativeCanvasWrapper, bitmap.getNativeInstance(), left, top,
                 paint != null ? paint.getNativeInstance() : 0, mDensity, mScreenDensity,
                 bitmap.mDensity);
     }
 
     public void drawBitmap(@NonNull Bitmap bitmap, @NonNull Matrix matrix, @Nullable Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawBitmapMatrix(mNativeCanvasWrapper, bitmap.getNativeInstance(), matrix.ni(),
                 paint != null ? paint.getNativeInstance() : 0);
     }
@@ -137,7 +137,7 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
 
         int left, top, right, bottom;
@@ -163,7 +163,7 @@
             throw new NullPointerException();
         }
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
 
         float left, top, right, bottom;
@@ -202,7 +202,7 @@
                 || (lastScanline + width > length)) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         // quick escape if there's nothing to draw
         if (width == 0 || height == 0) {
             return;
@@ -226,7 +226,7 @@
         if ((meshWidth | meshHeight | vertOffset | colorOffset) < 0) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (meshWidth == 0 || meshHeight == 0) {
             return;
         }
@@ -243,7 +243,7 @@
     }
 
     public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
     }
 
@@ -275,23 +275,23 @@
 
     public void drawLine(float startX, float startY, float stopX, float stopY,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
     }
 
     public void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawLines(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
     }
 
     public void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawLines(pts, 0, pts.length, paint);
     }
 
     public void drawOval(float left, float top, float right, float bottom, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawOval(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
     }
 
@@ -299,18 +299,19 @@
         if (oval == null) {
             throw new NullPointerException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawOval(oval.left, oval.top, oval.right, oval.bottom, paint);
     }
 
     public void drawPaint(@NonNull Paint paint) {
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPaint(mNativeCanvasWrapper, paint.getNativeInstance());
     }
 
     public void drawPatch(@NonNull NinePatch patch, @NonNull Rect dst, @Nullable Paint paint) {
         Bitmap bitmap = patch.getBitmap();
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
         nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint,
@@ -320,7 +321,7 @@
     public void drawPatch(@NonNull NinePatch patch, @NonNull RectF dst, @Nullable Paint paint) {
         Bitmap bitmap = patch.getBitmap();
         throwIfCannotDraw(bitmap);
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
         nDrawNinePatch(mNativeCanvasWrapper, bitmap.getNativeInstance(), patch.mNativeChunk,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint,
@@ -328,7 +329,7 @@
     }
 
     public void drawPath(@NonNull Path path, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (path.isSimplePath && path.rects != null) {
             nDrawRegion(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
         } else {
@@ -337,18 +338,18 @@
     }
 
     public void drawPoint(float x, float y, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoint(mNativeCanvasWrapper, x, y, paint.getNativeInstance());
     }
 
     public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawPoints(mNativeCanvasWrapper, pts, offset, count, paint.getNativeInstance());
     }
 
     public void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawPoints(pts, 0, pts.length, paint);
     }
 
@@ -359,7 +360,7 @@
         if (index < 0 || index + count > text.length || count * 2 > pos.length) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         for (int i = 0; i < count; i++) {
             drawText(text, index + i, 1, pos[i * 2], pos[i * 2 + 1], paint);
         }
@@ -368,22 +369,22 @@
     @Deprecated
     public void drawPosText(@NonNull String text, @NonNull @Size(multiple = 2) float[] pos,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawPosText(text.toCharArray(), 0, text.length(), pos, paint);
     }
 
     public void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
     }
 
     public void drawRect(@NonNull Rect r, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawRect(r.left, r.top, r.right, r.bottom, paint);
     }
 
     public void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRect(mNativeCanvasWrapper,
                 rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance());
     }
@@ -394,13 +395,13 @@
 
     public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
             @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawRoundRect(mNativeCanvasWrapper, left, top, right, bottom, rx, ry,
                 paint.getNativeInstance());
     }
 
     public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
     }
 
@@ -410,7 +411,7 @@
      */
     public void drawDoubleRoundRect(@NonNull RectF outer, float outerRx, float outerRy,
             @NonNull RectF inner, float innerRx, float innerRy, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         float outerLeft = outer.left;
         float outerTop = outer.top;
         float outerRight = outer.right;
@@ -431,7 +432,7 @@
      */
     public void drawDoubleRoundRect(@NonNull RectF outer, @NonNull float[] outerRadii,
             @NonNull RectF inner, @NonNull float[] innerRadii, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (innerRadii == null || outerRadii == null
                 || innerRadii.length != 8 || outerRadii.length != 8) {
             throw new IllegalArgumentException("Both inner and outer radii arrays must contain "
@@ -509,7 +510,7 @@
                 (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, index, count, x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -519,7 +520,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
@@ -537,7 +538,7 @@
     }
 
     public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, 0, text.length(), x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -547,7 +548,7 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawText(mNativeCanvasWrapper, text, start, end, x, y, paint.mBidiFlags,
                 paint.getNativeInstance());
     }
@@ -557,7 +558,7 @@
         if (index < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawTextOnPath(mNativeCanvasWrapper, text, index, count,
                 path.readOnlyNI(), hOffset, vOffset,
                 paint.mBidiFlags, paint.getNativeInstance());
@@ -566,7 +567,7 @@
     public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
             float vOffset, @NonNull Paint paint) {
         if (text.length() > 0) {
-            throwIfHasHwBitmapInSwMode(paint);
+            throwIfHasHwFeaturesInSwMode(paint);
             nDrawTextOnPath(mNativeCanvasWrapper, text, path.readOnlyNI(), hOffset, vOffset,
                     paint.mBidiFlags, paint.getNativeInstance());
         }
@@ -587,7 +588,7 @@
             throw new IndexOutOfBoundsException();
         }
 
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
                 x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */);
     }
@@ -606,7 +607,7 @@
             throw new IndexOutOfBoundsException();
         }
 
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawTextRun(mNativeCanvasWrapper, text.toString(), start, end, contextStart,
@@ -664,7 +665,7 @@
         if (indices != null) {
             checkRange(indices.length, indexOffset, indexCount);
         }
-        throwIfHasHwBitmapInSwMode(paint);
+        throwIfHasHwFeaturesInSwMode(paint);
         nDrawVertices(mNativeCanvasWrapper, mode.nativeInt, vertexCount, verts,
                 vertOffset, texs, texOffset, colors, colorOffset,
                 indices, indexOffset, indexCount, paint.getNativeInstance());
@@ -680,50 +681,52 @@
     /**
      * @hide
      */
-    public void setHwBitmapsInSwModeEnabled(boolean enabled) {
-        mAllowHwBitmapsInSwMode = enabled;
+    public void setHwFeaturesInSwModeEnabled(boolean enabled) {
+        mAllowHwFeaturesInSwMode = enabled;
     }
 
     /**
      * @hide
      */
-    public boolean isHwBitmapsInSwModeEnabled() {
-        return mAllowHwBitmapsInSwMode;
+    public boolean isHwFeaturesInSwModeEnabled() {
+        return mAllowHwFeaturesInSwMode;
     }
 
     /**
+     * If true throw an exception
      * @hide
      */
-    protected void onHwBitmapInSwMode() {
-        if (!mAllowHwBitmapsInSwMode) {
+    protected boolean onHwFeatureInSwMode() {
+        return !mAllowHwFeaturesInSwMode;
+    }
+
+    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
+        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE
+                && onHwFeatureInSwMode()) {
             throw new IllegalArgumentException(
                     "Software rendering doesn't support hardware bitmaps");
         }
     }
 
-    private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
-        if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
-            onHwBitmapInSwMode();
-        }
-    }
-
-    private void throwIfHasHwBitmapInSwMode(Paint p) {
+    private void throwIfHasHwFeaturesInSwMode(Paint p) {
         if (isHardwareAccelerated() || p == null) {
             return;
         }
-        throwIfHasHwBitmapInSwMode(p.getShader());
+        throwIfHasHwFeaturesInSwMode(p.getShader());
     }
 
-    private void throwIfHasHwBitmapInSwMode(Shader shader) {
+    private void throwIfHasHwFeaturesInSwMode(Shader shader) {
         if (shader == null) {
             return;
         }
         if (shader instanceof BitmapShader) {
             throwIfHwBitmapInSwMode(((BitmapShader) shader).mBitmap);
-        }
-        if (shader instanceof ComposeShader) {
-            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderA);
-            throwIfHasHwBitmapInSwMode(((ComposeShader) shader).mShaderB);
+        } else if (shader instanceof RuntimeShader && onHwFeatureInSwMode()) {
+            throw new IllegalArgumentException(
+                    "Software rendering doesn't support RuntimeShader");
+        } else if (shader instanceof ComposeShader) {
+            throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderA);
+            throwIfHasHwFeaturesInSwMode(((ComposeShader) shader).mShaderB);
         }
     }
 
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 390d3d4..ee4165b 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -124,7 +124,7 @@
     public void endRecording() {
         verifyValid();
         if (mRecordingCanvas != null) {
-            mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
+            mRequiresHwAcceleration = mRecordingCanvas.mUsesHwFeature;
             mRecordingCanvas = null;
             nativeEndRecording(mNativePicture);
         }
@@ -182,8 +182,10 @@
         if (mRecordingCanvas != null) {
             endRecording();
         }
-        if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) {
-            canvas.onHwBitmapInSwMode();
+        if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()
+                && canvas.onHwFeatureInSwMode()) {
+            throw new IllegalArgumentException("Software rendering not supported for Pictures that"
+                    + " require hardware acceleration");
         }
         nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
     }
@@ -242,7 +244,7 @@
 
     private static class PictureCanvas extends Canvas {
         private final Picture mPicture;
-        boolean mHoldsHwBitmap;
+        boolean mUsesHwFeature;
 
         public PictureCanvas(Picture pict, long nativeCanvas) {
             super(nativeCanvas);
@@ -265,8 +267,9 @@
         }
 
         @Override
-        protected void onHwBitmapInSwMode() {
-            mHoldsHwBitmap = true;
+        protected boolean onHwFeatureInSwMode() {
+            mUsesHwFeature = true;
+            return false;
         }
     }
 }
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 1ace322..9ca8e3b 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -16,13 +16,15 @@
 
 package android.graphics;
 
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
 import android.annotation.NonNull;
 
 import libcore.util.NativeAllocationRegistry;
 
 /**
- * Shader that calculates pixel output with a program (fragment shader) running on a GPU.
- * @hide
+ * Shader that calculates per-pixel color via a user defined Android Graphics Shading Language
+ * (AGSL) function.
  */
 public class RuntimeShader extends Shader {
 
@@ -32,7 +34,7 @@
                 RuntimeShader.class.getClassLoader(), nativeGetFinalizer());
     }
 
-    private boolean mIsOpaque;
+    private boolean mForceOpaque;
 
     /**
      * Current native shader builder instance.
@@ -42,57 +44,270 @@
     /**
      * Creates a new RuntimeShader.
      *
-     * @param sksl The text of SKSL program to run on the GPU.
-     * @param uniforms Array of parameters passed by the SKSL shader. Array size depends
-     *                 on number of uniforms declared by sksl.
-     * @param isOpaque True if all pixels have alpha 1.0f.
+     * @param shader The text of AGSL shader program to run.
      */
-    public RuntimeShader(@NonNull String sksl, boolean isOpaque) {
+    public RuntimeShader(@NonNull String shader) {
+        this(shader, false);
+    }
+
+    /**
+     * Creates a new RuntimeShader.
+     *
+     * @param shader The text of AGSL shader program to run.
+     * @param forceOpaque If true then all pixels produced by the AGSL shader program will have an
+     *                    alpha of 1.0f.
+     */
+    public RuntimeShader(@NonNull String shader, boolean forceOpaque) {
+        // colorspace is required, but the RuntimeShader always produces colors in the destination
+        // buffer's colorspace regardless of the value specified here.
         super(ColorSpace.get(ColorSpace.Named.SRGB));
-        mIsOpaque = isOpaque;
-        mNativeInstanceRuntimeShaderBuilder = nativeCreateBuilder(sksl);
+        if (shader == null) {
+            throw new NullPointerException("RuntimeShader requires a non-null AGSL string");
+        }
+        mForceOpaque = forceOpaque;
+        mNativeInstanceRuntimeShaderBuilder = nativeCreateBuilder(shader);
         NoImagePreloadHolder.sRegistry.registerNativeAllocation(
                 this, mNativeInstanceRuntimeShaderBuilder);
     }
 
+    public boolean isForceOpaque() {
+        return mForceOpaque;
+    }
+
     /**
-     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
-     * with that name or if the uniform is declared with a type other than float then an
-     * IllegalArgumentException is thrown.
+     * Sets the uniform color value corresponding to this shader.  If the shader does not have a
+     * uniform with that name or if the uniform is declared with a type other than vec3 or vec4 and
+     * corresponding layout(color) annotation then an IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the uniform declared in the SKSL shader
-     * @param value
+     * @param uniformName name matching the color uniform declared in the AGSL shader program
+     * @param color the provided sRGB color will be transformed into the shader program's output
+     *              colorspace and will be available as a vec4 uniform in the program.
      */
-    public void setUniform(@NonNull String uniformName, float value) {
-        setUniform(uniformName, new float[] {value});
+    public void setColorUniform(@NonNull String uniformName, @ColorInt int color) {
+        setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to this shader.  If the shader does not have a
+     * uniform with that name or if the uniform is declared with a type other than vec3 or vec4 and
+     * corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL shader program
+     * @param color the provided sRGB color will be transformed into the shader program's output
+     *              colorspace and will be available as a vec4 uniform in the program.
+     */
+    public void setColorUniform(@NonNull String uniformName, @ColorLong 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 this shader.  If the shader does not have a
+     * uniform with that name or if the uniform is declared with a type other than vec3 or vec4 and
+     * corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL shader program
+     * @param color the provided sRGB color will be transformed into the shader program's output
+     *              colorspace and will be available as a vec4 uniform in the program.
+     */
+    public void setColorUniform(@NonNull String uniformName, @NonNull 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 value corresponding to this shader.  If the shader does not have a uniform
-     * with that name or if the uniform is declared with a type other than float2/vec2 then an
-     * IllegalArgumentException is thrown.
+     * with that name or if the uniform is declared with a type other than a float or float[1]
+     * then an IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the uniform declared in the SKSL shader
-     * @param value1
-     * @param value2
+     * @param uniformName name matching the uniform declared in the AGSL shader program
      */
-    public void setUniform(@NonNull String uniformName, float value1, float value2) {
-        setUniform(uniformName, new float[] {value1, value2});
+    public void setFloatUniform(@NonNull String uniformName, float value) {
+        setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
     }
 
     /**
      * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
-     * with that name or if the uniform is declared with a type other than a vecN/floatN where N is
-     * the size of the values array then an IllegalArgumentException is thrown.
+     * with that name or if the uniform is declared with a type other than vec2 or float[2] then an
+     * IllegalArgumentException is thrown.
      *
-     * @param uniformName name matching the uniform declared in the SKSL shader
-     * @param values
+     * @param uniformName name matching the uniform declared in the AGSL shader program
      */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
+        setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than vec3 or float[3] then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3) {
+        setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than vec4 or float[4] then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3, float value4) {
+        setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than a float (for N=1), vecN,
+     * or float[N] where N is the length of the values param then an IllegalArgumentException is
+     * thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
+        setUniform(uniformName, values, false);
+    }
+
+    /**
+     * Old method signature used by some callers within the platform code
+     * @hide
+     * @deprecated use setFloatUniform instead
+     */
+    @Deprecated
     public void setUniform(@NonNull String uniformName, float[] values) {
+        setFloatUniform(uniformName, values);
+    }
+
+    /**
+     * Old method signature used by some callers within the platform code
+     * @hide
+     * @deprecated use setFloatUniform instead
+     */
+    @Deprecated
+    public void setUniform(@NonNull String uniformName, float value) {
+        setFloatUniform(uniformName, value);
+    }
+
+    /**
+     * Old method signature used by some callers within the platform code
+     * @hide
+     * @deprecated use setFloatUniform instead
+     */
+    @Deprecated
+    public void setUniform(@NonNull String uniformName, float value1, float value2) {
+        setFloatUniform(uniformName, value1, value2);
+    }
+
+    private void setFloatUniform(@NonNull 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(mNativeInstanceRuntimeShaderBuilder, uniformName, value1, value2,
+                value3, value4, count);
+        discardNativeInstance();
+    }
+
+    private void setUniform(@NonNull String uniformName, @NonNull 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(mNativeInstanceRuntimeShaderBuilder, uniformName, values, isColor);
+        discardNativeInstance();
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than an int or int[1]
+     * then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value) {
+        setIntUniform(uniformName, value, 0, 0, 0, 1);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than ivec2 or int[2] then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
+        setIntUniform(uniformName, value1, value2, 0, 0, 2);
+
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than ivec3 or int[3] then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
+        setIntUniform(uniformName, value1, value2, value3, 0, 3);
+
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than ivec4 or int[4] then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2,
+            int value3, int value4) {
+        setIntUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this shader.  If the shader does not have a uniform
+     * with that name or if the uniform is declared with a type other than an int (for N=1), ivecN,
+     * or int[N] where N is the length of the values param then an IllegalArgumentException is
+     * thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL shader program
+     */
+    public void setIntUniform(@NonNull String uniformName, @NonNull 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(mNativeInstanceRuntimeShaderBuilder, uniformName, values);
         discardNativeInstance();
     }
 
+    private void setIntUniform(@NonNull 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(mNativeInstanceRuntimeShaderBuilder, uniformName, value1, value2,
+                value3, value4, count);
+        discardNativeInstance();
+    }
+
     /**
      * Sets the uniform shader that is declares as input to this shader.  If the shader does not
      * have a uniform shader with that name then an IllegalArgumentException is thrown.
@@ -101,6 +316,12 @@
      * @param shader shader passed into the SKSL shader for sampling
      */
     public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) {
+        if (shaderName == null) {
+            throw new NullPointerException("The shaderName parameter must not be null");
+        }
+        if (shader == null) {
+            throw new NullPointerException("The shader parameter must not be null");
+        }
         nativeUpdateShader(
                     mNativeInstanceRuntimeShaderBuilder, shaderName, shader.getNativeInstance());
         discardNativeInstance();
@@ -109,23 +330,28 @@
     /** @hide */
     @Override
     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
-        return nativeCreateShader(mNativeInstanceRuntimeShaderBuilder, nativeMatrix, mIsOpaque);
+        return nativeCreateShader(mNativeInstanceRuntimeShaderBuilder, nativeMatrix, mForceOpaque);
     }
 
-    public long getNativeShaderBuilder() {
+    /** @hide */
+    protected long getNativeShaderBuilder() {
         return mNativeInstanceRuntimeShaderBuilder;
     }
 
-    public boolean isOpaque() {
-        return mIsOpaque;
-    }
-
     private static native long nativeGetFinalizer();
-    private static native long nativeCreateBuilder(String sksl);
+    private static native long nativeCreateBuilder(String agsl);
     private static native long nativeCreateShader(
             long shaderBuilder, long matrix, boolean isOpaque);
     private static native void nativeUpdateUniforms(
-            long shaderBuilder, String uniformName, float[] uniforms);
+            long shaderBuilder, String uniformName, float[] uniforms, boolean isColor);
+    private static native void nativeUpdateUniforms(
+            long shaderBuilder, String uniformName, float value1, float value2, float value3,
+            float value4, int count);
+    private static native void nativeUpdateUniforms(
+            long shaderBuilder, String uniformName, int[] uniforms);
+    private static native void nativeUpdateUniforms(
+            long shaderBuilder, String uniformName, int value1, int value2, int value3,
+            int value4, int count);
     private static native void nativeUpdateShader(
             long shaderBuilder, String shaderName, long shader);
 }
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 30389a29d..6db2745 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -16,25 +16,448 @@
 
 package android.security;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.security.keymaster.KeymasterDefs;
+import android.system.keystore2.ResponseCode;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
- * KeyStore/keymaster exception with positive error codes coming from the KeyStore and negative
- * ones from keymaster.
+ * Exception containing information about the failure at the Keystore / KeyMint layer while
+ * generating or using a key.
  *
- * @hide
+ * The public error codes indicate the cause of the error and the methods indicate whether
+ * it's a system/key issue and whether re-trying the operation (with the same key or a new key)
+ * is likely to succeed.
  */
-@TestApi
 public class KeyStoreException extends Exception {
+    /**
+     * This error code is for mapping errors that the caller will not know about. If the caller is
+     * targeting an API level earlier than the one the error was introduced in, then the error will
+     * be mapped to this one.
+     * In API level 33 no errors map to this error.
+     */
+    public static final int ERROR_OTHER = 1;
+    /**
+     * Indicating the key could not be used because the user needs to authenticate first.
+     * See
+     * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserAuthenticationRequired(boolean)}.
+     */
+    public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2;
+    /**
+     * Indicating that {@code load()} has not been called on the Keystore instance, or an attempt
+     * has been made to generate an authorization bound key while the user has not set a lock
+     * screen knowledge factor (LSKF). Instruct the user to set an LSKF and retry.
+     */
+    public static final int ERROR_KEYSTORE_UNINITIALIZED = 3;
+    /**
+     * An internal system error - refer to {@link #isTransientFailure()} to determine whether
+     * re-trying the operation is likely to yield different results.
+     */
+    public static final int ERROR_INTERNAL_SYSTEM_ERROR = 4;
+    /**
+     * The caller has requested key parameters or operation which are only available to system
+     * or privileged apps.
+     */
+    public static final int ERROR_PERMISSION_DENIED = 5;
+    /**
+     * The key the operation refers to doesn't exist.
+     */
+    public static final int ERROR_KEY_DOES_NOT_EXIST = 6;
+    /**
+     * The key is corrupted and could not be recovered.
+     */
+    public static final int ERROR_KEY_CORRUPTED = 7;
+    /**
+     * The error related to inclusion of device identifiers in the attestation record.
+     */
+    public static final int ERROR_ID_ATTESTATION_FAILURE = 8;
+    /**
+     * The attestation challenge specified is too large.
+     */
+    public static final int ERROR_ATTESTATION_CHALLENGE_TOO_LARGE = 9;
+    /**
+     * General error in the KeyMint layer.
+     */
+    public static final int ERROR_KEYMINT_FAILURE = 10;
+    /**
+     * Failure in the Keystore layer.
+     */
+    public static final int ERROR_KEYSTORE_FAILURE = 11;
+    /**
+     * The feature the caller is trying to use is not implemented by the underlying
+     * KeyMint implementation.
+     * This could happen when an unsupported algorithm is requested, or when trying to import
+     * a key in a format other than raw or PKCS#8.
+     */
+    public static final int ERROR_UNIMPLEMENTED = 12;
+    /**
+     * The feature the caller is trying to use is not compatible with the parameters used to
+     * generate the key. For example, trying to use a key generated for a different signature
+     * algorithm, or a digest not specified during key creation.
+     * Another case is the attempt to generate a symmetric AES key and requesting key attestation.
+     */
+    public static final int ERROR_INCORRECT_USAGE = 13;
+    /**
+     * The key is not currently valid: Either at has expired or it will be valid for use in the
+     * future.
+     */
+    public static final int ERROR_KEY_NOT_TEMPORALLY_VALID = 14;
+    /**
+     * The crypto object the caller has been using held a reference to a KeyMint operation that
+     * has been evacuated (likely due to other concurrent operations taking place).
+     * The caller should re-create the crypto object and try again.
+     */
+    public static final int ERROR_KEY_OPERATION_EXPIRED = 15;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = {"ERROR_"}, value = {
+            ERROR_OTHER,
+            ERROR_USER_AUTHENTICATION_REQUIRED,
+            ERROR_KEYSTORE_UNINITIALIZED,
+            ERROR_INTERNAL_SYSTEM_ERROR,
+            ERROR_PERMISSION_DENIED,
+            ERROR_KEY_DOES_NOT_EXIST,
+            ERROR_KEY_CORRUPTED,
+            ERROR_ID_ATTESTATION_FAILURE,
+            ERROR_ATTESTATION_CHALLENGE_TOO_LARGE,
+            ERROR_KEYMINT_FAILURE,
+            ERROR_KEYSTORE_FAILURE,
+            ERROR_UNIMPLEMENTED,
+            ERROR_INCORRECT_USAGE,
+            ERROR_KEY_NOT_TEMPORALLY_VALID,
+            ERROR_KEY_OPERATION_EXPIRED
+    })
+    public @interface PublicErrorCode {
+    }
+
+    // Constants for encoding information about the error encountered:
+    // Whether the error relates to the system state/implementation as a whole, or a specific key.
+    private static final int IS_SYSTEM_ERROR = 1 << 1;
+    // Whether the error is permanent.
+    private static final int IS_TRANSIENT_ERROR = 1 << 2;
+    // Whether the cause of the error is the user not having authenticated recently.
+    private static final int REQUIRES_USER_AUTHENTICATION = 1 << 3;
+
+    // The internal error code. NOT to be returned directly to callers or made part of the
+    // public API.
     private final int mErrorCode;
 
-    public KeyStoreException(int errorCode, String message) {
+    /**
+     * @hide
+     */
+    public KeyStoreException(int errorCode, @Nullable String message) {
         super(message);
         mErrorCode = errorCode;
     }
 
+    /**
+     * Returns the internal error code. Only for use by the platform.
+     *
+     * @hide
+     */
+    @TestApi
     public int getErrorCode() {
         return mErrorCode;
     }
+
+    /**
+     * Returns one of the error codes exported by the class.
+     *
+     * @return a public error code, one of the values in {@link PublicErrorCode}.
+     */
+    @PublicErrorCode
+    public int getNumericErrorCode() {
+        PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
+        return failureInfo.errorCode;
+    }
+
+    /**
+     * Returns true if the failure is a transient failure - that is, performing the same operation
+     * again at a late time is likely to succeed.
+     *
+     * If {@link #isSystemError()} returns true, the transient nature of the failure relates to the
+     * device, otherwise relates to the key (so a permanent failure with an existing key likely
+     * requires creating another key to repeat the operation with).
+     */
+    public boolean isTransientFailure() {
+        PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
+        return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0;
+    }
+
+    /**
+     * Indicates whether the failure is due to the device being locked.
+     *
+     * @return true if the key operation failed because the user has to authenticate
+     * (e.g. by unlocking the device).
+     */
+    public boolean requiresUserAuthentication() {
+        PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
+        return (failureInfo.indicators & REQUIRES_USER_AUTHENTICATION) != 0;
+    }
+
+    /**
+     * Indicates whether the error related to the Keystore/KeyMint implementation and not
+     * a specific key.
+     *
+     * @return true if the error is related to the system, not the key in use. System
+     * errors indicate a feature isn't working, whereas key-related errors are likely
+     * to succeed with a new key.
+     */
+    public boolean isSystemError() {
+        PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
+        return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
+    }
+
+    @Override
+    public String toString() {
+        String errorCodes = String.format(" (public error code: %d internal Keystore code: %d)",
+                getNumericErrorCode(), mErrorCode);
+        return super.toString() + errorCodes;
+    }
+
+    private static PublicErrorInformation getErrorInformation(int internalErrorCode) {
+        PublicErrorInformation errorInfo = sErrorCodeToFailureInfo.get(internalErrorCode);
+        if (errorInfo != null) {
+            return errorInfo;
+        }
+
+        /**
+         * KeyStore/keymaster exception with positive error codes coming from the KeyStore and
+         * negative ones from keymaster.
+         * This is a safety fall-back: All error codes should be present in the map.
+         */
+        if (internalErrorCode > 0) {
+            return GENERAL_KEYSTORE_ERROR;
+        } else {
+            return GENERAL_KEYMINT_ERROR;
+        }
+    }
+
+    private static final class PublicErrorInformation {
+        public final int indicators;
+        public final int errorCode;
+
+        PublicErrorInformation(int indicators, @PublicErrorCode int errorCode) {
+            this.indicators = indicators;
+            this.errorCode = errorCode;
+        }
+    }
+
+    private static final PublicErrorInformation GENERAL_KEYMINT_ERROR =
+            new PublicErrorInformation(0, ERROR_KEYMINT_FAILURE);
+
+    private static final PublicErrorInformation GENERAL_KEYSTORE_ERROR =
+            new PublicErrorInformation(0, ERROR_KEYSTORE_FAILURE);
+
+    private static final PublicErrorInformation KEYMINT_UNIMPLEMENTED_ERROR =
+            new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_UNIMPLEMENTED);
+
+    private static final PublicErrorInformation KEYMINT_RETRYABLE_ERROR =
+            new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                    ERROR_KEYMINT_FAILURE);
+
+    private static final PublicErrorInformation KEYMINT_INCORRECT_USAGE_ERROR =
+            new PublicErrorInformation(0, ERROR_INCORRECT_USAGE);
+
+    private static final PublicErrorInformation KEYMINT_TEMPORAL_VALIDITY_ERROR =
+            new PublicErrorInformation(0, ERROR_KEY_NOT_TEMPORALLY_VALID);
+
+
+    private static final Map<Integer, PublicErrorInformation> sErrorCodeToFailureInfo =
+            new HashMap();
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static boolean hasFailureInfoForError(int internalErrorCode) {
+        return sErrorCodeToFailureInfo.containsKey(internalErrorCode);
+    }
+
+    static {
+        // KeyMint error codes
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_OK, GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_ROOT_OF_TRUST_ALREADY_SET,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_PURPOSE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_PURPOSE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_ALGORITHM,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_ALGORITHM,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_KEY_SIZE,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_BLOCK_MODE,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_BLOCK_MODE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_MAC_LENGTH,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_PADDING_MODE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_PADDING_MODE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_DIGEST,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_DIGEST,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_EXPIRATION_TIME,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_USER_ID,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_KEY_FORMAT,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_KEY_FORMAT,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_INPUT_LENGTH,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEY_EXPORT_OPTIONS_INVALID,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_DELEGATION_NOT_ALLOWED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID,
+                KEYMINT_TEMPORAL_VALIDITY_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEY_EXPIRED,
+                KEYMINT_TEMPORAL_VALIDITY_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEY_USER_NOT_AUTHENTICATED,
+                new PublicErrorInformation(REQUIRES_USER_AUTHENTICATION,
+                        ERROR_USER_AUTHENTICATION_REQUIRED));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_OUTPUT_PARAMETER_NULL,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_KEY_OPERATION_EXPIRED));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INSUFFICIENT_BUFFER_SPACE,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_VERIFICATION_FAILED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_TOO_MANY_OPERATIONS,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNEXPECTED_NULL_POINTER,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_KEY_BLOB,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_IMPORTED_KEY_NOT_SIGNED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_ARGUMENT,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_TAG,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_TAG,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MEMORY_ALLOCATION_FAILED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_IMPORT_PARAMETER_MISMATCH,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_SECURE_HW_ACCESS_DENIED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_OPERATION_CANCELLED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_CONCURRENT_ACCESS_CONFLICT,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_SECURE_HW_BUSY,
+                KEYMINT_RETRYABLE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_SECURE_HW_COMMUNICATION_FAILED,
+                KEYMINT_RETRYABLE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_EC_FIELD,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_NONCE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_NONCE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_MAC_LENGTH,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEY_RATE_LIMIT_EXCEEDED,
+                KEYMINT_RETRYABLE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED,
+                GENERAL_KEYMINT_ERROR);
+        // Error related to MAX_USES_PER_BOOT, restricting the number of uses per boot.
+        // It is not re-tryable.
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEY_MAX_OPS_EXCEEDED,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INVALID_MAC_LENGTH,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_MIN_MAC_LENGTH,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_KDF,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_EC_CURVE,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_ATTESTATION_CHALLENGE_MISSING,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_KEYMINT_NOT_CONFIGURED,
+                new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_KEYMINT_FAILURE));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_ATTESTATION_APPLICATION_ID_MISSING,
+                KEYMINT_RETRYABLE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_CANNOT_ATTEST_IDS,
+                new PublicErrorInformation(IS_SYSTEM_ERROR,
+                        ERROR_ID_ATTESTATION_FAILURE));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_ROLLBACK_RESISTANCE_UNAVAILABLE,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_DEVICE_LOCKED,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | REQUIRES_USER_AUTHENTICATION,
+                        ERROR_USER_AUTHENTICATION_REQUIRED));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_STORAGE_KEY_UNSUPPORTED,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_INCOMPATIBLE_MGF_DIGEST,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNSUPPORTED_MGF_DIGEST,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_NOT_BEFORE,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_MISSING_NOT_AFTER,
+                KEYMINT_INCORRECT_USAGE_ERROR);
+        // This should not be exposed to apps as it's handled by Keystore.
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_HARDWARE_NOT_YET_AVAILABLE,
+                GENERAL_KEYMINT_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNIMPLEMENTED,
+                KEYMINT_UNIMPLEMENTED_ERROR);
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR,
+                new PublicErrorInformation(IS_SYSTEM_ERROR,
+                        ERROR_KEYMINT_FAILURE));
+        sErrorCodeToFailureInfo.put(KeymasterDefs.KM_ERROR_VERSION_MISMATCH, GENERAL_KEYMINT_ERROR);
+
+        // Keystore error codes
+        sErrorCodeToFailureInfo.put(ResponseCode.LOCKED,
+                new PublicErrorInformation(REQUIRES_USER_AUTHENTICATION,
+                        ERROR_USER_AUTHENTICATION_REQUIRED));
+        sErrorCodeToFailureInfo.put(ResponseCode.UNINITIALIZED,
+                new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_KEYSTORE_UNINITIALIZED));
+        sErrorCodeToFailureInfo.put(ResponseCode.SYSTEM_ERROR,
+                new PublicErrorInformation(IS_SYSTEM_ERROR,
+                        ERROR_INTERNAL_SYSTEM_ERROR));
+        sErrorCodeToFailureInfo.put(ResponseCode.PERMISSION_DENIED,
+                new PublicErrorInformation(0, ERROR_PERMISSION_DENIED));
+        sErrorCodeToFailureInfo.put(ResponseCode.KEY_NOT_FOUND,
+                new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
+        sErrorCodeToFailureInfo.put(ResponseCode.VALUE_CORRUPTED,
+                new PublicErrorInformation(0, ERROR_KEY_CORRUPTED));
+        sErrorCodeToFailureInfo.put(ResponseCode.KEY_PERMANENTLY_INVALIDATED,
+                new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
+    }
 }
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 fe6c7ba..b8e8b01 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -91,11 +91,13 @@
      */
     public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
             @Nullable Bundle options, @NonNull SplitRule sideRule,
-            @NonNull Consumer<Exception> failureCallback) {
+            @Nullable Consumer<Exception> failureCallback) {
         try {
             mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule);
         } catch (Exception e) {
-            failureCallback.accept(e);
+            if (failureCallback != null) {
+                failureCallback.accept(e);
+            }
         }
     }
 
@@ -293,11 +295,12 @@
             @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
             @NonNull SplitRule splitRule) {
+        SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
+                secondaryContainer, splitRule);
+        // Remove container later to prevent pinning escaping toast showing in lock task mode.
         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
             removeExistingSecondaryContainers(wct, primaryContainer);
         }
-        SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
-                secondaryContainer, splitRule);
         mSplitContainers.add(splitContainer);
     }
 
@@ -858,4 +861,12 @@
                     launchingContainer.getTaskFragmentToken());
         }
     }
+
+    /**
+     * Checks if an activity is embedded and its presentation is customized by a
+     * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
+     */
+    public boolean isActivityEmbedded(@NonNull Activity activity) {
+        return mPresenter.isActivityEmbedded(activity.getActivityToken());
+    }
 }
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 81be21c..ade5731 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -112,8 +112,7 @@
         secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
 
         // Set adjacent to each other so that the containers below will be invisible.
-        setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
-                secondaryContainer.getTaskFragmentToken(), rule);
+        setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
 
         mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
 
@@ -149,8 +148,7 @@
                 secondaryActivity, secondaryRectBounds, primaryContainer);
 
         // Set adjacent to each other so that the containers below will be invisible.
-        setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
-                secondaryContainer.getTaskFragmentToken(), rule);
+        setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
 
         mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
 
@@ -269,8 +267,22 @@
         final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
         resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
 
-        setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
-                secondaryContainer.getTaskFragmentToken(), rule);
+        setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule);
+    }
+
+    private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentContainer primaryContainer,
+            @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) {
+        final Rect parentBounds = getParentContainerBounds(primaryContainer);
+        // Clear adjacent TaskFragments if the container is shown in fullscreen, or the
+        // secondaryContainer could not be finished.
+        if (!shouldShowSideBySide(parentBounds, splitRule)) {
+            setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+                    null /* secondary */, null /* splitRule */);
+        } else {
+            setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
+                    secondaryContainer.getTaskFragmentToken(), splitRule);
+        }
     }
 
     /**
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 8c8ef92..46bdf6d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -16,6 +16,7 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
 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;
@@ -29,7 +30,7 @@
 import android.animation.ValueAnimator;
 import android.graphics.Rect;
 import android.os.Handler;
-import android.os.Looper;
+import android.os.HandlerThread;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -50,10 +51,14 @@
 class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
 
     private static final String TAG = "TaskFragAnimationRunner";
-    private final Handler mHandler = new Handler(Looper.myLooper());
+    private final Handler mHandler;
     private final TaskFragmentAnimationSpec mAnimationSpec;
 
     TaskFragmentAnimationRunner() {
+        HandlerThread animationThread = new HandlerThread(
+                "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
+        animationThread.start();
+        mHandler = animationThread.getThreadHandler();
         mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
     }
 
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 a1a53bc..4d2d055 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -103,7 +103,7 @@
         ActivityThread activityThread = ActivityThread.currentActivityThread();
         for (IBinder token : mInfo.getActivities()) {
             Activity activity = activityThread.getActivity(token);
-            if (activity != null && !allActivities.contains(activity)) {
+            if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) {
                 allActivities.add(activity);
             }
         }
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index d6678bf..f54ab08 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
similarity index 65%
copy from libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
copy to libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
index 22cd384..2758704 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon.xml
@@ -14,8 +14,10 @@
   ~ 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="rectangle">
-    <solid android:color="@color/size_compat_background"/>
-    <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+          android:color="@color/tv_pip_menu_icon_focused" />
+    <item android:state_enabled="false"
+          android:color="@color/tv_pip_menu_icon_disabled" />
+    <item android:color="@color/tv_pip_menu_icon_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
similarity index 72%
copy from libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
copy to libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
index 22cd384..4f5e63d 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/libs/WindowManager/Shell/res/color/tv_pip_menu_icon_bg.xml
@@ -14,8 +14,8 @@
   ~ 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="rectangle">
-    <solid android:color="@color/size_compat_background"/>
-    <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"
+          android:color="@color/tv_pip_menu_icon_bg_focused" />
+    <item android:color="@color/tv_pip_menu_icon_bg_unfocused" />
+</selector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml
similarity index 85%
rename from libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
rename to libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml
index 22cd384..26848b1 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/libs/WindowManager/Shell/res/drawable/compat_hint_bubble.xml
@@ -16,6 +16,6 @@
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
-    <solid android:color="@color/size_compat_background"/>
-    <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
+    <solid android:color="@color/compat_controls_background"/>
+    <corners android:radius="@dimen/compat_hint_corner_radius"/>
 </shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/drawable/compat_hint_point.xml
similarity index 88%
rename from libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
rename to libs/WindowManager/Shell/res/drawable/compat_hint_point.xml
index af9063a..0e0ca37 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
+++ b/libs/WindowManager/Shell/res/drawable/compat_hint_point.xml
@@ -15,11 +15,11 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/size_compat_hint_point_width"
+        android:width="@dimen/compat_hint_point_width"
         android:height="8dp"
         android:viewportWidth="10"
         android:viewportHeight="8">
     <path
-        android:fillColor="@color/size_compat_background"
+        android:fillColor="@color/compat_controls_background"
         android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/>
 </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 18caa35..ab74e43 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -20,16 +20,16 @@
         android:viewportWidth="48"
         android:viewportHeight="48">
     <path
-        android:fillColor="@color/size_compat_background"
+        android:fillColor="@color/compat_controls_background"
         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">
         <path
-            android:fillColor="@color/size_compat_text"
+            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/size_compat_text"
+            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"/>
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
similarity index 74%
rename from libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml
rename to libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
index cce1303..1938f45 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_pip_button_focused.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_button_bg.xml
@@ -14,5 +14,8 @@
     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="#9AFFFFFF" android:radius="17dp" />
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners android:radius="@dimen/pip_menu_button_radius" />
+    <solid android:color="@color/tv_pip_menu_icon_bg" />
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
similarity index 77%
copy from libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
copy to libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
index 22cd384..9bc0311 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_pip_menu_border.xml
@@ -15,7 +15,8 @@
   ~ limitations under the License.
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
-       android:shape="rectangle">
-    <solid android:color="@color/size_compat_background"/>
-    <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
+    android:shape="rectangle">
+    <corners android:radius="@dimen/pip_menu_border_radius" />
+    <stroke android:width="@dimen/pip_menu_border_width"
+            android:color="@color/tv_pip_menu_focus_border" />
 </shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
new file mode 100644
index 0000000..c04e258e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/compat_mode_hint.xml
@@ -0,0 +1,46 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:clipToPadding="false"
+    android:paddingEnd="@dimen/compat_hint_padding_end"
+    android:paddingBottom="5dp"
+    android:clickable="true">
+
+    <TextView
+        android:id="@+id/compat_mode_hint_text"
+        android:layout_width="188dp"
+        android:layout_height="wrap_content"
+        android:lineSpacingExtra="4sp"
+        android:background="@drawable/compat_hint_bubble"
+        android:padding="16dp"
+        android:textAlignment="viewStart"
+        android:textColor="@color/compat_controls_text"
+        android:textSize="14sp"/>
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:src="@drawable/compat_hint_point"
+        android:paddingHorizontal="@dimen/compat_hint_corner_radius"
+        android:contentDescription="@null"/>
+
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
similarity index 82%
rename from libs/WindowManager/Shell/res/layout/size_compat_ui.xml
rename to libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index 82ebee2..6f946b2 100644
--- a/libs/WindowManager/Shell/res/layout/size_compat_ui.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -14,10 +14,15 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.wm.shell.sizecompatui.SizeCompatRestartButton
+<com.android.wm.shell.compatui.CompatUILayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="bottom|end">
+
+    <include android:id="@+id/size_compat_hint"
+         layout="@layout/compat_mode_hint"/>
 
     <FrameLayout
         android:layout_width="@dimen/size_compat_button_width"
@@ -36,4 +41,4 @@
 
     </FrameLayout>
 
-</com.android.wm.shell.sizecompatui.SizeCompatRestartButton>
+</com.android.wm.shell.compatui.CompatUILayout>
diff --git a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml b/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
deleted file mode 100644
index d0e7c42..0000000
--- a/libs/WindowManager/Shell/res/layout/size_compat_mode_hint.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?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.
--->
-<com.android.wm.shell.sizecompatui.SizeCompatHintPopup
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content">
-
-    <FrameLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:clipToPadding="false"
-        android:paddingBottom="5dp">
-
-        <LinearLayout
-            android:id="@+id/size_compat_hint_popup"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:clickable="true">
-
-            <TextView
-                android:layout_width="188dp"
-                android:layout_height="wrap_content"
-                android:lineSpacingExtra="4sp"
-                android:background="@drawable/size_compat_hint_bubble"
-                android:padding="16dp"
-                android:text="@string/restart_button_description"
-                android:textAlignment="viewStart"
-                android:textColor="@color/size_compat_text"
-                android:textSize="14sp"/>
-
-            <ImageView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="end"
-                android:src="@drawable/size_compat_hint_point"
-                android:paddingHorizontal="@dimen/size_compat_hint_corner_radius"
-                android:contentDescription="@null"/>
-
-        </LinearLayout>
-
-    </FrameLayout>
-
-</com.android.wm.shell.sizecompatui.SizeCompatHintPopup>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 49e2379..5b90c99 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -18,35 +18,54 @@
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/tv_pip_menu"
              android:layout_width="match_parent"
-             android:layout_height="match_parent"
-             android:background="#CC000000">
+             android:layout_height="match_parent">
 
-    <LinearLayout
-        android:id="@+id/tv_pip_menu_action_buttons"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:layout_marginTop="350dp"
-        android:orientation="horizontal"
-        android:alpha="0">
+    <FrameLayout
+        android:id="@+id/tv_pip_menu_frame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:alpha="0" >
 
-        <com.android.wm.shell.pip.tv.TvPipMenuActionButton
-            android:id="@+id/tv_pip_menu_fullscreen_button"
-            android:layout_width="@dimen/picture_in_picture_button_width"
-            android:layout_height="wrap_content"
-            android:src="@drawable/pip_ic_fullscreen_white"
-            android:text="@string/pip_fullscreen" />
+        <HorizontalScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_centerHorizontal="true"
+            android:gravity="center"
+            android:scrollbars="none"
+            android:requiresFadingEdge="vertical"
+            android:fadingEdgeLength="30dp">
 
-        <com.android.wm.shell.pip.tv.TvPipMenuActionButton
-            android:id="@+id/tv_pip_menu_close_button"
-            android:layout_width="@dimen/picture_in_picture_button_width"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
-            android:src="@drawable/pip_ic_close_white"
-            android:text="@string/pip_close" />
+            <LinearLayout
+                android:id="@+id/tv_pip_menu_action_buttons"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:paddingStart="@dimen/pip_menu_button_wrapper_margin"
+                android:paddingEnd="@dimen/pip_menu_button_wrapper_margin"
+                android:gravity="center"
+                android:orientation="horizontal">
 
-        <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_fullscreen_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_fullscreen_white"
+                    android:text="@string/pip_fullscreen" />
 
-    </LinearLayout>
+                <com.android.wm.shell.pip.tv.TvPipMenuActionButton
+                    android:id="@+id/tv_pip_menu_close_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/pip_ic_close_white"
+                    android:text="@string/pip_close" />
 
+                <!-- More TvPipMenuActionButtons may be added here at runtime. -->
+
+            </LinearLayout>
+        </HorizontalScrollView>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:background="@drawable/tv_pip_menu_border"/>
+    </FrameLayout>
 </FrameLayout>
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
index 5925008..f9d0968 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu_action_button.xml
@@ -15,36 +15,20 @@
     limitations under the License.
 -->
 <!-- Layout for TvPipMenuActionButton -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/button"
+        android:layout_width="@dimen/pip_menu_button_size"
+        android:layout_height="@dimen/pip_menu_button_size"
+        android:layout_marginStart="@dimen/pip_menu_button_margin"
+        android:layout_marginEnd="@dimen/pip_menu_button_margin"
+        android:background="@drawable/tv_pip_button_bg"
+        android:focusable="true">
 
-    <ImageView android:id="@+id/button"
-        android:layout_width="34dp"
-        android:layout_height="34dp"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true"
-        android:focusable="true"
-        android:src="@drawable/tv_pip_button_focused"
-        android:importantForAccessibility="yes" />
-
-    <ImageView android:id="@+id/icon"
-        android:layout_width="34dp"
-        android:layout_height="34dp"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true"
-        android:padding="5dp"
-        android:importantForAccessibility="no" />
-
-    <TextView android:id="@+id/desc"
-        android:layout_width="100dp"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/icon"
-        android:layout_centerHorizontal="true"
-        android:layout_marginTop="3dp"
-        android:gravity="center"
-        android:text="@string/pip_fullscreen"
-        android:alpha="0"
-        android:fontFamily="sans-serif"
-        android:textSize="12sp"
-        android:textColor="#EEEEEE"
-        android:importantForAccessibility="no" />
-</merge>
+        <ImageView android:id="@+id/icon"
+                   android:layout_width="@dimen/pip_menu_icon_size"
+                   android:layout_height="@dimen/pip_menu_icon_size"
+                   android:layout_gravity="center"
+                   android:duplicateParentState="true"
+                   android:tint="@color/tv_pip_menu_icon" />
+</FrameLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml
deleted file mode 100644
index bf4eb26..0000000
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu_additional_action_button.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?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.
--->
-<com.android.wm.shell.pip.tv.TvPipMenuActionButton
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/picture_in_picture_button_width"
-    android:layout_height="wrap_content"
-    android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" />
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 7920fd2..e41ebc4 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -16,7 +16,12 @@
 -->
 <resources>
     <!-- The dimensions to user for picture-in-picture action buttons. -->
-    <dimen name="picture_in_picture_button_width">100dp</dimen>
-    <dimen name="picture_in_picture_button_start_margin">-50dp</dimen>
+    <dimen name="pip_menu_button_size">40dp</dimen>
+    <dimen name="pip_menu_button_radius">20dp</dimen>
+    <dimen name="pip_menu_icon_size">20dp</dimen>
+    <dimen name="pip_menu_button_margin">4dp</dimen>
+    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
+    <dimen name="pip_menu_border_width">2dp</dimen>
+    <dimen name="pip_menu_border_radius">0dp</dimen>
 </resources>
 
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 23a2172..cf596f7 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -30,9 +30,9 @@
     <color name="bubbles_dark">@color/GM2_grey_800</color>
     <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
 
-    <!-- Size Compat Restart Button -->
-    <color name="size_compat_background">@android:color/system_neutral1_800</color>
-    <color name="size_compat_text">@android:color/system_neutral1_50</color>
+    <!-- Compat controls UI -->
+    <color name="compat_controls_background">@android:color/system_neutral1_800</color>
+    <color name="compat_controls_text">@android:color/system_neutral1_50</color>
 
     <!-- GM2 colors -->
     <color name="GM2_grey_200">#E8EAED</color>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/libs/WindowManager/Shell/res/values/colors_tv.xml
similarity index 62%
copy from libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
copy to libs/WindowManager/Shell/res/values/colors_tv.xml
index af9063a..17387fa 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
+++ b/libs/WindowManager/Shell/res/values/colors_tv.xml
@@ -14,12 +14,11 @@
   ~ 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="@dimen/size_compat_hint_point_width"
-        android:height="8dp"
-        android:viewportWidth="10"
-        android:viewportHeight="8">
-    <path
-        android:fillColor="@color/size_compat_background"
-        android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/>
-</vector>
+<resources>
+    <color name="tv_pip_menu_icon_focused">#0E0E0F</color>
+    <color name="tv_pip_menu_icon_unfocused">#E8EAED</color>
+    <color name="tv_pip_menu_icon_disabled">#80868B</color>
+    <color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
+    <color name="tv_pip_menu_icon_bg_unfocused">#777777</color>
+    <color name="tv_pip_menu_focus_border">#CCE8EAED</color>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 9e77578..18e91f4 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -206,11 +206,15 @@
     <!-- The height of the size compat restart button including padding. -->
     <dimen name="size_compat_button_height">64dp</dimen>
 
-    <!-- The radius of the corners of the size compat hint bubble. -->
-    <dimen name="size_compat_hint_corner_radius">28dp</dimen>
+    <!-- The radius of the corners of the compat hint bubble. -->
+    <dimen name="compat_hint_corner_radius">28dp</dimen>
 
-    <!-- The width of the size compat hint point. -->
-    <dimen name="size_compat_hint_point_width">10dp</dimen>
+    <!-- The width of the compat hint point. -->
+    <dimen name="compat_hint_point_width">10dp</dimen>
+
+    <!-- The end padding for the compat hint. Computed as (size_compat_button_width / 2
+         - compat_hint_corner_radius - compat_hint_point_width /2). -->
+    <dimen name="compat_hint_padding_end">7dp</dimen>
 
     <!-- The width of the brand image on staring surface. -->
     <dimen name="starting_surface_brand_image_width">200dp</dimen>
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 8e98b82..8b3a356 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -52,8 +52,8 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.startingsurface.StartingWindowController;
 
 import java.io.PrintWriter;
@@ -69,7 +69,7 @@
  * TODO(b/167582004): may consider consolidating this class and TaskOrganizer
  */
 public class ShellTaskOrganizer extends TaskOrganizer implements
-        SizeCompatUIController.SizeCompatUICallback {
+        CompatUIController.CompatUICallback {
 
     // Intentionally using negative numbers here so the positive numbers can be used
     // for task id specific listeners that will be added later.
@@ -98,9 +98,9 @@
         default void onTaskInfoChanged(RunningTaskInfo taskInfo) {}
         default void onTaskVanished(RunningTaskInfo taskInfo) {}
         default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
-        /** Whether this task listener supports size compat UI. */
-        default boolean supportSizeCompatUI() {
-            // All TaskListeners should support size compat except PIP.
+        /** Whether this task listener supports  compat UI. */
+        default boolean supportCompatUI() {
+            // All TaskListeners should support compat UI except PIP.
             return true;
         }
         /** Attaches the a child window surface to the task surface. */
@@ -159,11 +159,11 @@
     private StartingWindowController mStartingWindow;
 
     /**
-     * In charge of showing size compat UI. Can be {@code null} if device doesn't support size
+     * In charge of showing compat UI. Can be {@code null} if device doesn't support size
      * compat.
      */
     @Nullable
-    private final SizeCompatUIController mSizeCompatUI;
+    private final CompatUIController mCompatUI;
 
     @Nullable
     private final Optional<RecentTasksController> mRecentTasks;
@@ -172,32 +172,32 @@
     private RunningTaskInfo mLastFocusedTaskInfo;
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
-        this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */,
+        this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
                 Optional.empty() /* recentTasksController */);
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            SizeCompatUIController sizeCompatUI) {
-        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+            CompatUIController compatUI) {
+        this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
                 Optional.empty() /* recentTasksController */);
     }
 
     public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
-            SizeCompatUIController sizeCompatUI,
+            CompatUIController compatUI,
             Optional<RecentTasksController> recentTasks) {
-        this(null /* taskOrganizerController */, mainExecutor, context, sizeCompatUI,
+        this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
                 recentTasks);
     }
 
     @VisibleForTesting
     ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
-            Context context, @Nullable SizeCompatUIController sizeCompatUI,
+            Context context, @Nullable CompatUIController compatUI,
             Optional<RecentTasksController> recentTasks) {
         super(taskOrganizerController, mainExecutor);
-        mSizeCompatUI = sizeCompatUI;
+        mCompatUI = compatUI;
         mRecentTasks = recentTasks;
-        if (sizeCompatUI != null) {
-            sizeCompatUI.setSizeCompatUICallback(this);
+        if (compatUI != null) {
+            compatUI.setCompatUICallback(this);
         }
     }
 
@@ -428,7 +428,7 @@
             listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
         }
         notifyLocusVisibilityIfNeeded(info.getTaskInfo());
-        notifySizeCompatUI(info.getTaskInfo(), listener);
+        notifyCompatUI(info.getTaskInfo(), listener);
     }
 
     /**
@@ -459,8 +459,8 @@
             }
             notifyLocusVisibilityIfNeeded(taskInfo);
             if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) {
-                // Notify the size compat UI if the listener or task info changed.
-                notifySizeCompatUI(taskInfo, newListener);
+                // Notify the compat UI if the listener or task info changed.
+                notifyCompatUI(taskInfo, newListener);
             }
             if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) {
                 // Notify the recent tasks when a task changes windowing modes
@@ -504,8 +504,8 @@
                 listener.onTaskVanished(taskInfo);
             }
             notifyLocusVisibilityIfNeeded(taskInfo);
-            // Pass null for listener to remove the size compat UI on this task if there is any.
-            notifySizeCompatUI(taskInfo, null /* taskListener */);
+            // Pass null for listener to remove the compat UI on this task if there is any.
+            notifyCompatUI(taskInfo, null /* taskListener */);
             // Notify the recent tasks that a task has been removed
             mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo));
         }
@@ -618,28 +618,28 @@
     }
 
     /**
-     * Notifies {@link SizeCompatUIController} about the size compat info changed on the give Task
+     * Notifies {@link CompatUIController} about the compat info changed on the give Task
      * to update the UI accordingly.
      *
      * @param taskInfo the new Task info
      * @param taskListener listener to handle the Task Surface placement. {@code null} if task is
      *                     vanished.
      */
-    private void notifySizeCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
-        if (mSizeCompatUI == null) {
+    private void notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) {
+        if (mCompatUI == null) {
             return;
         }
 
-        // The task is vanished or doesn't support size compat UI, notify to remove size compat UI
+        // The task is vanished or doesn't support compat UI, notify to remove compat UI
         // on this Task if there is any.
-        if (taskListener == null || !taskListener.supportSizeCompatUI()
+        if (taskListener == null || !taskListener.supportCompatUI()
                 || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) {
-            mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+            mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
                     null /* taskConfig */, null /* taskListener */);
             return;
         }
 
-        mSizeCompatUI.onSizeCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
+        mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId,
                 taskInfo.configuration, taskListener);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 2f3214d..54e743f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,6 +41,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
@@ -77,6 +78,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Executor mShellExecutor;
     private final SyncTransactionQueue mSyncQueue;
+    private final TaskViewTransitions mTaskViewTransitions;
 
     private ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mTaskToken;
@@ -92,17 +94,27 @@
     private final Rect mTmpRootRect = new Rect();
     private final int[] mTmpLocation = new int[2];
 
-    public TaskView(Context context, ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue) {
+    public TaskView(Context context, ShellTaskOrganizer organizer,
+            TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) {
         super(context, null, 0, 0, true /* disableBackgroundLayer */);
 
         mTaskOrganizer = organizer;
         mShellExecutor = organizer.getExecutor();
         mSyncQueue = syncQueue;
+        mTaskViewTransitions = taskViewTransitions;
+        if (mTaskViewTransitions != null) {
+            mTaskViewTransitions.addTaskView(this);
+        }
         setUseAlpha();
         getHolder().addCallback(this);
         mGuard.open("release");
     }
 
+    /** Until all users are converted, we may have mixed-use (eg. Car). */
+    private boolean isUsingShellTransitions() {
+        return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+    }
+
     /**
      * Only one listener may be set on the view, throws an exception otherwise.
      */
@@ -129,6 +141,14 @@
             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
         prepareActivityOptions(options, launchBounds);
         LauncherApps service = mContext.getSystemService(LauncherApps.class);
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle());
+                mTaskViewTransitions.startTaskView(wct, this);
+            });
+            return;
+        }
         try {
             service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle());
         } catch (Exception e) {
@@ -148,6 +168,14 @@
     public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
             @NonNull ActivityOptions options, @Nullable Rect launchBounds) {
         prepareActivityOptions(options, launchBounds);
+        if (isUsingShellTransitions()) {
+            mShellExecutor.execute(() -> {
+                WindowContainerTransaction wct = new WindowContainerTransaction();
+                wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle());
+                mTaskViewTransitions.startTaskView(wct, this);
+            });
+            return;
+        }
         try {
             pendingIntent.send(mContext, 0 /* code */, fillInIntent,
                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
@@ -177,6 +205,16 @@
         mObscuredTouchRect = obscuredRect;
     }
 
+    private void onLocationChanged(WindowContainerTransaction wct) {
+        // Update based on the screen bounds
+        getBoundsOnScreen(mTmpRect);
+        getRootView().getBoundsOnScreen(mTmpRootRect);
+        if (!mTmpRootRect.contains(mTmpRect)) {
+            mTmpRect.offsetTo(0, 0);
+        }
+        wct.setBounds(mTaskToken, mTmpRect);
+    }
+
     /**
      * Call when view position or size has changed. Do not call when animating.
      */
@@ -184,15 +222,12 @@
         if (mTaskToken == null) {
             return;
         }
-        // Update based on the screen bounds
-        getBoundsOnScreen(mTmpRect);
-        getRootView().getBoundsOnScreen(mTmpRootRect);
-        if (!mTmpRootRect.contains(mTmpRect)) {
-            mTmpRect.offsetTo(0, 0);
-        }
+        // Sync Transactions can't operate simultaneously with shell transition collection.
+        // The transition animation (upon showing) will sync the location itself.
+        if (isUsingShellTransitions() && mTaskViewTransitions.hasPending()) return;
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setBounds(mTaskToken, mTmpRect);
+        onLocationChanged(wct);
         mSyncQueue.queue(wct);
     }
 
@@ -217,6 +252,9 @@
 
     private void performRelease() {
         getHolder().removeCallback(this);
+        if (mTaskViewTransitions != null) {
+            mTaskViewTransitions.removeTaskView(this);
+        }
         mShellExecutor.execute(() -> {
             mTaskOrganizer.removeListener(this);
             resetTaskInfo();
@@ -254,6 +292,10 @@
     @Override
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl leash) {
+        if (isUsingShellTransitions()) {
+            // Everything else handled by enter transition.
+            return;
+        }
         mTaskInfo = taskInfo;
         mTaskToken = taskInfo.token;
         mTaskLeash = leash;
@@ -288,6 +330,8 @@
 
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+        // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that
+        // we know about -- so leave clean-up here even if shell transitions are enabled.
         if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return;
 
         if (mListener != null) {
@@ -355,6 +399,10 @@
                 // Nothing to update, task is not yet available
                 return;
             }
+            if (isUsingShellTransitions()) {
+                mTaskViewTransitions.setTaskViewVisible(this, true /* visible */);
+                return;
+            }
             // Reparent the task when this surface is created
             mTransaction.reparent(mTaskLeash, getSurfaceControl())
                     .show(mTaskLeash)
@@ -380,6 +428,11 @@
                 return;
             }
 
+            if (isUsingShellTransitions()) {
+                mTaskViewTransitions.setTaskViewVisible(this, false /* visible */);
+                return;
+            }
+
             // Unparent the task when this surface is destroyed
             mTransaction.reparent(mTaskLeash, null).apply();
             updateTaskVisibility();
@@ -421,4 +474,91 @@
         super.onDetachedFromWindow();
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
     }
+
+    ActivityManager.RunningTaskInfo getTaskInfo() {
+        return mTaskInfo;
+    }
+
+    void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) {
+        if (mTaskToken == null) {
+            // Nothing to update, task is not yet available
+            return;
+        }
+
+        finishTransaction.reparent(mTaskLeash, null).apply();
+
+        if (mListener != null) {
+            final int taskId = mTaskInfo.taskId;
+            mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
+        }
+    }
+
+    /**
+     * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide
+     * is used instead.
+     */
+    void prepareCloseAnimation() {
+        if (mTaskToken != null) {
+            if (mListener != null) {
+                final int taskId = mTaskInfo.taskId;
+                mListenerExecutor.execute(() -> {
+                    mListener.onTaskRemovalStarted(taskId);
+                });
+            }
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, false);
+        }
+        resetTaskInfo();
+    }
+
+    void prepareOpenAnimation(final boolean newTask,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
+            WindowContainerTransaction wct) {
+        mTaskInfo = taskInfo;
+        mTaskToken = mTaskInfo.token;
+        mTaskLeash = leash;
+        if (mSurfaceCreated) {
+            // Surface is ready, so just reparent the task to this surface control
+            startTransaction.reparent(mTaskLeash, getSurfaceControl())
+                    .show(mTaskLeash)
+                    .apply();
+            // Also reparent on finishTransaction since the finishTransaction will reparent back
+            // to its "original" parent by default.
+            finishTransaction.reparent(mTaskLeash, getSurfaceControl())
+                    .setPosition(mTaskLeash, 0, 0)
+                    .apply();
+
+            // TODO: determine if this is really necessary or not
+            onLocationChanged(wct);
+        } else {
+            // The surface has already been destroyed before the task has appeared,
+            // so go ahead and hide the task entirely
+            wct.setHidden(mTaskToken, true /* hidden */);
+            // listener callback is below
+        }
+        if (newTask) {
+            mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */);
+        }
+
+        if (mTaskInfo.taskDescription != null) {
+            int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor();
+            setResizeBackgroundColor(startTransaction, backgroundColor);
+        }
+
+        if (mListener != null) {
+            final int taskId = mTaskInfo.taskId;
+            final ComponentName baseActivity = mTaskInfo.baseActivity;
+
+            mListenerExecutor.execute(() -> {
+                if (newTask) {
+                    mListener.onTaskCreated(taskId, baseActivity);
+                }
+                // Even if newTask, send a visibilityChange if the surface was destroyed.
+                if (!newTask || !mSurfaceCreated) {
+                    mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */);
+                }
+            });
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
index 8286d10..42844b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewFactoryController.java
@@ -31,13 +31,24 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final ShellExecutor mShellExecutor;
     private final SyncTransactionQueue mSyncQueue;
+    private final TaskViewTransitions mTaskViewTransitions;
     private final TaskViewFactory mImpl = new TaskViewFactoryImpl();
 
     public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
+            ShellExecutor shellExecutor, SyncTransactionQueue syncQueue,
+            TaskViewTransitions taskViewTransitions) {
+        mTaskOrganizer = taskOrganizer;
+        mShellExecutor = shellExecutor;
+        mSyncQueue = syncQueue;
+        mTaskViewTransitions = taskViewTransitions;
+    }
+
+    public TaskViewFactoryController(ShellTaskOrganizer taskOrganizer,
             ShellExecutor shellExecutor, SyncTransactionQueue syncQueue) {
         mTaskOrganizer = taskOrganizer;
         mShellExecutor = shellExecutor;
         mSyncQueue = syncQueue;
+        mTaskViewTransitions = null;
     }
 
     public TaskViewFactory asTaskViewFactory() {
@@ -46,7 +57,7 @@
 
     /** Creates an {@link TaskView} */
     public void create(@UiContext Context context, Executor executor, Consumer<TaskView> onCreate) {
-        TaskView taskView = new TaskView(context, mTaskOrganizer, mSyncQueue);
+        TaskView taskView = new TaskView(context, mTaskOrganizer, mTaskViewTransitions, mSyncQueue);
         executor.execute(() -> {
             onCreate.accept(taskView);
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
new file mode 100644
index 0000000..83335ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -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.wm.shell;
+
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.ArrayList;
+
+/**
+ * Handles Shell Transitions that involve TaskView tasks.
+ */
+public class TaskViewTransitions implements Transitions.TransitionHandler {
+    private static final String TAG = "TaskViewTransitions";
+
+    private final ArrayList<TaskView> mTaskViews = new ArrayList<>();
+    private final ArrayList<PendingTransition> mPending = new ArrayList<>();
+    private final Transitions mTransitions;
+    private final boolean[] mRegistered = new boolean[]{ false };
+
+    /**
+     * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be
+     * in-flight (collecting) at a time (because otherwise, the operations could get merged into
+     * a single transition). So, keep a queue here until we add a queue in server-side.
+     */
+    private static class PendingTransition {
+        final @WindowManager.TransitionType int mType;
+        final WindowContainerTransaction mWct;
+        final @NonNull TaskView mTaskView;
+        IBinder mClaimed;
+
+        PendingTransition(@WindowManager.TransitionType int type,
+                @Nullable WindowContainerTransaction wct, @NonNull TaskView taskView) {
+            mType = type;
+            mWct = wct;
+            mTaskView = taskView;
+        }
+    }
+
+    public TaskViewTransitions(Transitions transitions) {
+        mTransitions = transitions;
+        // Defer registration until the first TaskView because we want this to be the "first" in
+        // priority when handling requests.
+        // TODO(210041388): register here once we have an explicit ordering mechanism.
+    }
+
+    void addTaskView(TaskView tv) {
+        synchronized (mRegistered) {
+            if (!mRegistered[0]) {
+                mRegistered[0] = true;
+                mTransitions.addHandler(this);
+            }
+        }
+        mTaskViews.add(tv);
+    }
+
+    void removeTaskView(TaskView tv) {
+        mTaskViews.remove(tv);
+        // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
+    }
+
+    /**
+     * Looks through the pending transitions for one matching `taskView`.
+     * @param taskView the pending transition should be for this.
+     * @param closing When true, this only returns a pending transition of the close/hide type.
+     *                Otherwise it selects open/show.
+     * @param latest When true, this will only check the most-recent pending transition for the
+     *               specified taskView. If it doesn't match `closing`, this will return null even
+     *               if there is a match earlier. The idea behind this is to check the state of
+     *               the taskviews "as if all transitions already happened".
+     */
+    private PendingTransition findPending(TaskView taskView, boolean closing, boolean latest) {
+        for (int i = mPending.size() - 1; i >= 0; --i) {
+            if (mPending.get(i).mTaskView != taskView) continue;
+            if (Transitions.isClosingType(mPending.get(i).mType) == closing) {
+                return mPending.get(i);
+            }
+            if (latest) {
+                return null;
+            }
+        }
+        return null;
+    }
+
+    private PendingTransition findPending(IBinder claimed) {
+        for (int i = 0; i < mPending.size(); ++i) {
+            if (mPending.get(i).mClaimed != claimed) continue;
+            return mPending.get(i);
+        }
+        return null;
+    }
+
+    /** @return whether there are pending transitions on TaskViews. */
+    public boolean hasPending() {
+        return !mPending.isEmpty();
+    }
+
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @Nullable TransitionRequestInfo request) {
+        final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+        if (triggerTask == null) {
+            return null;
+        }
+        final TaskView taskView = findTaskView(triggerTask);
+        if (taskView == null) return null;
+        // Opening types should all be initiated by shell
+        if (!Transitions.isClosingType(request.getType())) return null;
+        PendingTransition pending = findPending(taskView, true /* closing */, false /* latest */);
+        if (pending == null) {
+            pending = new PendingTransition(request.getType(), null, taskView);
+        }
+        if (pending.mClaimed != null) {
+            throw new IllegalStateException("Task is closing in 2 collecting transitions?"
+                    + " This state doesn't make sense");
+        }
+        pending.mClaimed = transition;
+        return new WindowContainerTransaction();
+    }
+
+    private TaskView findTaskView(ActivityManager.RunningTaskInfo taskInfo) {
+        for (int i = 0; i < mTaskViews.size(); ++i) {
+            if (mTaskViews.get(i).getTaskInfo() == null) continue;
+            if (taskInfo.token.equals(mTaskViews.get(i).getTaskInfo().token)) {
+                return mTaskViews.get(i);
+            }
+        }
+        return null;
+    }
+
+    void startTaskView(WindowContainerTransaction wct, TaskView taskView) {
+        mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView));
+        startNextTransition();
+    }
+
+    void setTaskViewVisible(TaskView taskView, boolean visible) {
+        PendingTransition pending = findPending(taskView, !visible, true /* latest */);
+        if (pending != null) {
+            // Already opening or creating a task, so no need to do anything here.
+            return;
+        }
+        if (taskView.getTaskInfo() == null) {
+            // Nothing to update, task is not yet available
+            return;
+        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */);
+        pending = new PendingTransition(
+                visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView);
+        mPending.add(pending);
+        startNextTransition();
+        // visibility is reported in transition.
+    }
+
+    private void startNextTransition() {
+        if (mPending.isEmpty()) return;
+        final PendingTransition pending = mPending.get(0);
+        if (pending.mClaimed != null) {
+            // Wait for this to start animating.
+            return;
+        }
+        pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this);
+    }
+
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition,
+            @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        PendingTransition pending = findPending(transition);
+        if (pending == null) return false;
+        mPending.remove(pending);
+        TaskView taskView = pending.mTaskView;
+        final ArrayList<TransitionInfo.Change> tasks = new ArrayList<>();
+        for (int i = 0; i < info.getChanges().size(); ++i) {
+            final TransitionInfo.Change chg = info.getChanges().get(i);
+            if (chg.getTaskInfo() == null) continue;
+            tasks.add(chg);
+        }
+        if (tasks.isEmpty()) {
+            Slog.e(TAG, "Got a TaskView transition with no task.");
+            return false;
+        }
+        WindowContainerTransaction wct = null;
+        for (int i = 0; i < tasks.size(); ++i) {
+            TransitionInfo.Change chg = tasks.get(i);
+            if (Transitions.isClosingType(chg.getMode())) {
+                final boolean isHide = chg.getMode() == TRANSIT_TO_BACK;
+                TaskView tv = findTaskView(chg.getTaskInfo());
+                if (tv == null) {
+                    throw new IllegalStateException("TaskView transition is closing a "
+                            + "non-taskview task ");
+                }
+                if (isHide) {
+                    tv.prepareHideAnimation(finishTransaction);
+                } else {
+                    tv.prepareCloseAnimation();
+                }
+            } else if (Transitions.isOpeningType(chg.getMode())) {
+                final boolean taskIsNew = chg.getMode() == TRANSIT_OPEN;
+                if (wct == null) wct = new WindowContainerTransaction();
+                TaskView tv = taskView;
+                if (!taskIsNew) {
+                    tv = findTaskView(chg.getTaskInfo());
+                    if (tv == null) {
+                        throw new IllegalStateException("TaskView transition is showing a "
+                            + "non-taskview task ");
+                    }
+                }
+                tv.prepareOpenAnimation(taskIsNew, startTransaction, finishTransaction,
+                        chg.getTaskInfo(), chg.getLeash(), wct);
+            } else {
+                throw new IllegalStateException("Claimed transition isn't an opening or closing"
+                        + " type: " + chg.getMode());
+            }
+        }
+        // No animation, just show it immediately.
+        startTransaction.apply();
+        finishTransaction.apply();
+        finishCallback.onTransitionFinished(wct, null /* wctCB */);
+        startNextTransition();
+        return true;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index d92e2cc..686fbbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -15,23 +15,22 @@
  */
 package com.android.wm.shell.bubbles;
 
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
+import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.PathParser;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.launcher3.icons.DotRenderer;
@@ -47,7 +46,7 @@
  * Badge = the icon associated with the app that created this bubble, this will show work profile
  * badge if appropriate.
  */
-public class BadgedImageView extends ImageView {
+public class BadgedImageView extends FrameLayout {
 
     /** Same value as Launcher3 dot code */
     public static final float WHITE_SCRIM_ALPHA = 0.54f;
@@ -74,6 +73,9 @@
     private final EnumSet<SuppressionFlag> mDotSuppressionFlags =
             EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE);
 
+    private final ImageView mBubbleIcon;
+    private final ImageView mAppIcon;
+
     private float mDotScale = 0f;
     private float mAnimatingToDotScale = 0f;
     private boolean mDotIsAnimating = false;
@@ -86,7 +88,6 @@
     private DotRenderer.DrawParams mDrawParams;
     private int mDotColor;
 
-    private Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
     private Rect mTempBounds = new Rect();
 
     public BadgedImageView(Context context) {
@@ -104,6 +105,17 @@
     public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+
+        mBubbleIcon = new ImageView(context);
+        addView(mBubbleIcon);
+        mAppIcon = new ImageView(context);
+        addView(mAppIcon);
+
+        final TypedArray ta = mContext.obtainStyledAttributes(attrs, new int[]{android.R.attr.src},
+                defStyleAttr, defStyleRes);
+        mBubbleIcon.setImageResource(ta.getResourceId(0, 0));
+        ta.recycle();
+
         mDrawParams = new DotRenderer.DrawParams();
 
         setFocusable(true);
@@ -135,7 +147,6 @@
     public void showDotAndBadge(boolean onLeft) {
         removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.BEHIND_STACK);
         animateDotBadgePositions(onLeft);
-
     }
 
     public void hideDotAndBadge(boolean onLeft) {
@@ -149,6 +160,7 @@
      */
     public void setRenderedBubble(BubbleViewProvider bubble) {
         mBubble = bubble;
+        mBubbleIcon.setImageBitmap(bubble.getBubbleIcon());
         if (mDotSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK)) {
             hideBadge();
         } else {
@@ -176,6 +188,20 @@
         mDotRenderer.draw(canvas, mDrawParams);
     }
 
+    /**
+     * Set drawable resource shown as the icon
+     */
+    public void setIconImageResource(@DrawableRes int drawable) {
+        mBubbleIcon.setImageResource(drawable);
+    }
+
+    /**
+     * Get icon drawable
+     */
+    public Drawable getIconDrawable() {
+        return mBubbleIcon.getDrawable();
+    }
+
     /** Adds a dot suppression flag, updating dot visibility if needed. */
     void addDotSuppressionFlag(SuppressionFlag flag) {
         if (mDotSuppressionFlags.add(flag)) {
@@ -279,7 +305,6 @@
         showBadge();
     }
 
-
     /** Whether to draw the dot in onDraw(). */
     private boolean shouldDrawDot() {
         // Always render the dot if it's animating, since it could be animating out. Otherwise, show
@@ -325,29 +350,28 @@
     void showBadge() {
         Bitmap badge = mBubble.getAppBadge();
         if (badge == null) {
-            setImageBitmap(mBubble.getBubbleIcon());
+            mAppIcon.setVisibility(GONE);
             return;
         }
-        Canvas bubbleCanvas = new Canvas();
-        Bitmap noBadgeBubble = mBubble.getBubbleIcon();
-        Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true);
 
-        bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
-        bubbleCanvas.setBitmap(bubble);
-        final int bubbleSize = bubble.getWidth();
+        final int bubbleSize = mBubble.getBubbleIcon().getWidth();
         final int badgeSize = (int) (ICON_BADGE_SCALE * bubbleSize);
-        Rect dest = new Rect();
+
+        FrameLayout.LayoutParams appIconParams = (LayoutParams) mAppIcon.getLayoutParams();
+        appIconParams.height = badgeSize;
+        appIconParams.width = badgeSize;
         if (mOnLeft) {
-            dest.set(0, bubbleSize - badgeSize, badgeSize, bubbleSize);
+            appIconParams.gravity = Gravity.BOTTOM | Gravity.LEFT;
         } else {
-            dest.set(bubbleSize - badgeSize, bubbleSize - badgeSize, bubbleSize, bubbleSize);
+            appIconParams.gravity = Gravity.BOTTOM | Gravity.RIGHT;
         }
-        bubbleCanvas.drawBitmap(badge, null /* src */, dest, mPaint);
-        bubbleCanvas.setBitmap(null);
-        setImageBitmap(bubble);
+        mAppIcon.setLayoutParams(appIconParams);
+
+        mAppIcon.setImageBitmap(badge);
+        mAppIcon.setVisibility(VISIBLE);
     }
 
     void hideBadge() {
-        setImageBitmap(mBubble.getBubbleIcon());
+        mAppIcon.setVisibility(GONE);
     }
 }
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 ec59fad..ce1f870 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
@@ -80,6 +80,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
@@ -88,6 +89,8 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 
 import java.io.FileDescriptor;
@@ -97,6 +100,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.function.IntConsumer;
@@ -136,6 +140,7 @@
     private final TaskStackListenerImpl mTaskStackListener;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final DisplayController mDisplayController;
+    private final TaskViewTransitions mTaskViewTransitions;
     private final SyncTransactionQueue mSyncQueue;
 
     // Used to post to main UI thread
@@ -196,6 +201,9 @@
     /** True when user is in status bar unlock shade. */
     private boolean mIsStatusBarShade = true;
 
+    /** One handed mode controller to register transition listener. */
+    private Optional<OneHandedController> mOneHandedOptional;
+
     /**
      * Creates an instance of the BubbleController.
      */
@@ -210,8 +218,10 @@
             UiEventLogger uiEventLogger,
             ShellTaskOrganizer organizer,
             DisplayController displayController,
+            Optional<OneHandedController> oneHandedOptional,
             ShellExecutor mainExecutor,
             Handler mainHandler,
+            TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
         BubbleLogger logger = new BubbleLogger(uiEventLogger);
         BubblePositioner positioner = new BubblePositioner(context, windowManager);
@@ -219,8 +229,9 @@
         return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                logger, taskStackListener, organizer, positioner, displayController, mainExecutor,
-                mainHandler, syncQueue);
+                logger, taskStackListener, organizer, positioner, displayController,
+                oneHandedOptional, mainExecutor, mainHandler, taskViewTransitions,
+                syncQueue);
     }
 
     /**
@@ -241,8 +252,10 @@
             ShellTaskOrganizer organizer,
             BubblePositioner positioner,
             DisplayController displayController,
+            Optional<OneHandedController> oneHandedOptional,
             ShellExecutor mainExecutor,
             Handler mainHandler,
+            TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
         mContext = context;
         mLauncherApps = launcherApps;
@@ -266,9 +279,26 @@
         mSavedBubbleKeysPerUser = new SparseSetArray<>();
         mBubbleIconFactory = new BubbleIconFactory(context);
         mDisplayController = displayController;
+        mTaskViewTransitions = taskViewTransitions;
+        mOneHandedOptional = oneHandedOptional;
         mSyncQueue = syncQueue;
     }
 
+    private static void registerOneHandedState(OneHandedController oneHanded) {
+        oneHanded.registerTransitionCallback(
+                new OneHandedTransitionCallback() {
+                    @Override
+                    public void onStartFinished(Rect bounds) {
+                        // TODO(b/198403767) mStackView.offSetY(int bounds.top)
+                    }
+
+                    @Override
+                    public void onStopFinished(Rect bounds) {
+                        // TODO(b/198403767) mStackView.offSetY(int bounds.top)
+                    }
+                });
+    }
+
     public void initialize() {
         mBubbleData.setListener(mBubbleDataListener);
         mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged);
@@ -392,6 +422,8 @@
                         }
                     }
                 });
+
+        mOneHandedOptional.ifPresent(BubbleController::registerOneHandedState);
     }
 
     @VisibleForTesting
@@ -570,6 +602,10 @@
         return mSyncQueue;
     }
 
+    TaskViewTransitions getTaskViewTransitions() {
+        return mTaskViewTransitions;
+    }
+
     /** Contains information to help position things on the screen.  */
     BubblePositioner getPositioner() {
         return mBubblePositioner;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 519a856..51b7eaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -328,6 +328,7 @@
         if (prevBubble == null) {
             // Create a new bubble
             bubble.setSuppressFlyout(suppressFlyout);
+            bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
             doAdd(bubble);
             trim();
         } else {
@@ -561,17 +562,10 @@
         overflowBubble(reason, bubbleToRemove);
 
         if (mBubbles.size() == 1) {
-            if (hasOverflowBubbles() && (mPositioner.showingInTaskbar() || isExpanded())) {
-                // No more active bubbles but we have stuff in the overflow -- select that view
-                // if we're already expanded or always showing.
-                setShowingOverflow(true);
-                setSelectedBubbleInternal(mOverflow);
-            } else {
-                setExpandedInternal(false);
-                // Don't use setSelectedBubbleInternal because we don't want to trigger an
-                // applyUpdate
-                mSelectedBubble = null;
-            }
+            setExpandedInternal(false);
+            // Don't use setSelectedBubbleInternal because we don't want to trigger an
+            // applyUpdate
+            mSelectedBubble = null;
         }
         if (indexToRemove < mBubbles.size() - 1) {
             // Removing anything but the last bubble means positions will change.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index a87aad4..af59062 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -335,7 +335,7 @@
             mManageButton.setVisibility(GONE);
         } else {
             mTaskView = new TaskView(mContext, mController.getTaskOrganizer(),
-                    mController.getSyncTransactionQueue());
+                    mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
             mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener);
             mExpandedViewContainer.addView(mTaskView);
             bringChildToFront(mTaskView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 5161092..a175929 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -66,7 +66,7 @@
         updateResources()
         getExpandedView()?.applyThemeAttrs()
         // Apply inset and new style to fresh icon drawable.
-        getIconView()?.setImageResource(R.drawable.bubble_ic_overflow_button)
+        getIconView()?.setIconImageResource(R.drawable.bubble_ic_overflow_button)
         updateBtnTheme()
     }
 
@@ -89,19 +89,19 @@
         dotColor = colorAccent
 
         val shapeColor = res.getColor(android.R.color.system_accent1_1000)
-        overflowBtn?.drawable?.setTint(shapeColor)
+        overflowBtn?.iconDrawable?.setTint(shapeColor)
 
         val iconFactory = BubbleIconFactory(context)
 
         // Update bitmap
-        val fg = InsetDrawable(overflowBtn?.drawable, overflowIconInset)
+        val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
         bitmap = iconFactory.createBadgedIconBitmap(
                 AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)).icon
 
         // Update dot path
         dotPath = PathParser.createPathFromPathData(
             res.getString(com.android.internal.R.string.config_icon_mask))
-        val scale = iconFactory.normalizer.getScale(iconView!!.drawable,
+        val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable,
             null /* outBounds */, null /* path */, null /* outMaskShape */)
         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
         val matrix = Matrix()
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 f0f78748..19d513f 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
@@ -346,6 +346,9 @@
      * bubble is dragged back into the row.
      */
     public void dragBubbleOut(View bubbleView, float x, float y) {
+        if (mMagnetizedBubbleDraggingOut == null) {
+            return;
+        }
         if (mSpringToTouchOnNextMotionEvent) {
             springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
             mSpringToTouchOnNextMotionEvent = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index ffda1f9..c32733d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -27,7 +27,6 @@
 
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
-import java.util.ArrayList;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -71,12 +70,18 @@
         mRotationListener.remove(listener);
     }
 
+    /** Query all listeners for changes that should happen on rotation. */
+    public void dispatchOnRotateDisplay(WindowContainerTransaction outWct, int displayId,
+            final int fromRotation, final int toRotation) {
+        for (OnDisplayChangingListener c : mRotationListener) {
+            c.onRotateDisplay(displayId, fromRotation, toRotation, outWct);
+        }
+    }
+
     private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
             IDisplayWindowRotationCallback callback) {
         WindowContainerTransaction t = new WindowContainerTransaction();
-        for (OnDisplayChangingListener c : mRotationListener) {
-            c.onRotateDisplay(displayId, fromRotation, toRotation, t);
-        }
+        dispatchOnRotateDisplay(t, displayId, fromRotation, toRotation);
         try {
             callback.continueRotateDisplay(toRotation, t);
         } catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index a1fb658..7db49f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -76,6 +76,11 @@
         }
     }
 
+    /** Get the DisplayChangeController. */
+    public DisplayChangeController getChangeController() {
+        return mChangeController;
+    }
+
     /**
      * Gets a display by id from DisplayManager.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
index eea6e3c..c4bd73b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/ScreenshotUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.common;
 
-import android.graphics.GraphicBuffer;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
@@ -63,8 +62,6 @@
             if (buffer == null || buffer.getHardwareBuffer() == null) {
                 return;
             }
-            final GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
-                buffer.getHardwareBuffer());
             mScreenshot = new SurfaceControl.Builder()
                 .setName("ScreenshotUtils screenshot")
                 .setFormat(PixelFormat.TRANSLUCENT)
@@ -73,7 +70,7 @@
                 .setBLASTLayer()
                 .build();
 
-            mTransaction.setBuffer(mScreenshot, graphicBuffer);
+            mTransaction.setBuffer(mScreenshot, buffer.getHardwareBuffer());
             mTransaction.setColorSpace(mScreenshot, buffer.getColorSpace());
             mTransaction.reparent(mScreenshot, mSurfaceControl);
             mTransaction.setLayer(mScreenshot, mLayer);
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 ba343cb..b8ac87f 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
@@ -93,7 +93,7 @@
     private final InsetsState mInsetsState = new InsetsState();
 
     private Context mContext;
-    private DividerSnapAlgorithm mDividerSnapAlgorithm;
+    @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
     private WindowContainerToken mWinToken1;
     private WindowContainerToken mWinToken2;
     private int mDividePosition;
@@ -294,20 +294,22 @@
         mSplitLayoutHandler.onLayoutSizeChanging(this);
     }
 
-    void setDividePosition(int position) {
+    void setDividePosition(int position, boolean applyLayoutChange) {
         mDividePosition = position;
         updateBounds(mDividePosition);
-        mSplitLayoutHandler.onLayoutSizeChanged(this);
+        if (applyLayoutChange) {
+            mSplitLayoutHandler.onLayoutSizeChanged(this);
+        }
     }
 
-    /** Sets divide position base on the ratio within root bounds. */
+    /** Updates divide position and split bounds base on the ratio within root bounds. */
     public void setDivideRatio(float ratio) {
         final int position = isLandscape()
                 ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
                 : mRootBounds.top + (int) (mRootBounds.height() * ratio);
-        DividerSnapAlgorithm.SnapTarget snapTarget =
+        final DividerSnapAlgorithm.SnapTarget snapTarget =
                 mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
-        setDividePosition(snapTarget.position);
+        setDividePosition(snapTarget.position, false /* applyLayoutChange */);
     }
 
     /** Resets divider position. */
@@ -335,7 +337,8 @@
                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */));
                 break;
             default:
-                flingDividePosition(currentPosition, snapTarget.position, null);
+                flingDividePosition(currentPosition, snapTarget.position,
+                        () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */));
                 break;
         }
     }
@@ -381,7 +384,6 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                setDividePosition(to);
                 if (flingFinishedCallback != null) {
                     flingFinishedCallback.run();
                 }
@@ -389,7 +391,7 @@
 
             @Override
             public void onAnimationCancel(Animator animation) {
-                setDividePosition(to);
+                setDividePosition(to, true /* applyLayoutChange */);
             }
         });
         animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
similarity index 86%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
index a703114..99dbfe0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUI.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
@@ -14,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
 
 /**
- * Interface to engage size compat UI.
+ * Interface to engage compat UI.
  */
 @ExternalThread
-public interface SizeCompatUI {
+public interface CompatUI {
     /**
-     * Called when the keyguard occluded state changes. Removes all size compat UIs if the
+     * Called when the keyguard occluded state changes. Removes all compat UIs if the
      * keyguard is now occluded.
      * @param occluded indicates if the keyguard is now occluded.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
similarity index 81%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index e06070a..e0b2387 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -48,20 +48,20 @@
 
 /**
  * Controls to show/update restart-activity buttons on Tasks based on whether the foreground
- * activities are in size compatibility mode.
+ * activities are in compatibility mode.
  */
-public class SizeCompatUIController implements OnDisplaysChangedListener,
+public class CompatUIController implements OnDisplaysChangedListener,
         DisplayImeController.ImePositionProcessor {
 
     /** Callback for size compat UI interaction. */
-    public interface SizeCompatUICallback {
+    public interface CompatUICallback {
         /** Called when the size compat restart button appears. */
         void onSizeCompatRestartButtonAppeared(int taskId);
         /** Called when the size compat restart button is clicked. */
         void onSizeCompatRestartButtonClicked(int taskId);
     }
 
-    private static final String TAG = "SizeCompatUIController";
+    private static final String TAG = "CompatUIController";
 
     /** Whether the IME is shown on display id. */
     private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);
@@ -71,7 +71,7 @@
             new SparseArray<>(0);
 
     /** The showing UIs by task id. */
-    private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);
+    private final SparseArray<CompatUIWindowManager> mActiveLayouts = new SparseArray<>(0);
 
     /** Avoid creating display context frequently for non-default display. */
     private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@@ -82,17 +82,17 @@
     private final DisplayImeController mImeController;
     private final SyncTransactionQueue mSyncQueue;
     private final ShellExecutor mMainExecutor;
-    private final SizeCompatUIImpl mImpl = new SizeCompatUIImpl();
+    private final CompatUIImpl mImpl = new CompatUIImpl();
 
-    private SizeCompatUICallback mCallback;
+    private CompatUICallback mCallback;
 
     /** Only show once automatically in the process life. */
     private boolean mHasShownHint;
-    /** Indicates if the keyguard is currently occluded, in which case size compat UIs shouldn't
+    /** Indicates if the keyguard is currently occluded, in which case compat UIs shouldn't
      * be shown. */
     private boolean mKeyguardOccluded;
 
-    public SizeCompatUIController(Context context,
+    public CompatUIController(Context context,
             DisplayController displayController,
             DisplayInsetsController displayInsetsController,
             DisplayImeController imeController,
@@ -108,35 +108,36 @@
         mImeController.addPositionProcessor(this);
     }
 
-    public SizeCompatUI asSizeCompatUI() {
+    /** Returns implementation of {@link CompatUI}. */
+    public CompatUI asCompatUI() {
         return mImpl;
     }
 
     /** Sets the callback for UI interactions. */
-    public void setSizeCompatUICallback(SizeCompatUICallback callback) {
+    public void setCompatUICallback(CompatUICallback callback) {
         mCallback = callback;
     }
 
     /**
-     * Called when the Task info changed. Creates and updates the size compat UI if there is an
+     * Called when the Task info changed. Creates and updates the compat UI if there is an
      * activity in size compat, or removes the UI if there is no size compat activity.
      *
      * @param displayId display the task and activity are in.
      * @param taskId task the activity is in.
-     * @param taskConfig task config to place the size compat UI with.
+     * @param taskConfig task config to place the compat UI with.
      * @param taskListener listener to handle the Task Surface placement.
      */
-    public void onSizeCompatInfoChanged(int displayId, int taskId,
+    public void onCompatInfoChanged(int displayId, int taskId,
             @Nullable Configuration taskConfig,
             @Nullable ShellTaskOrganizer.TaskListener taskListener) {
         if (taskConfig == null || taskListener == null) {
-            // Null token means the current foreground activity is not in size compatibility mode.
+            // Null token means the current foreground activity is not in compatibility mode.
             removeLayout(taskId);
         } else if (mActiveLayouts.contains(taskId)) {
             // UI already exists, update the UI layout.
             updateLayout(taskId, taskConfig, taskListener);
         } else {
-            // Create a new size compat UI.
+            // Create a new compat UI.
             createLayout(displayId, taskId, taskConfig, taskListener);
         }
     }
@@ -151,7 +152,7 @@
         mDisplayContextCache.remove(displayId);
         removeOnInsetsChangedListener(displayId);
 
-        // Remove all size compat UIs on the removed display.
+        // Remove all compat UIs on the removed display.
         final List<Integer> toRemoveTaskIds = new ArrayList<>();
         forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
         for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
@@ -194,7 +195,7 @@
             mDisplaysWithIme.remove(displayId);
         }
 
-        // Hide the size compat UIs when input method is showing.
+        // Hide the compat UIs when input method is showing.
         forAllLayoutsOnDisplay(displayId,
                 layout -> layout.updateVisibility(showOnDisplay(displayId)));
     }
@@ -202,7 +203,7 @@
     @VisibleForTesting
     void onKeyguardOccludedChanged(boolean occluded) {
         mKeyguardOccluded = occluded;
-        // Hide the size compat UIs when keyguard is occluded.
+        // Hide the compat UIs when keyguard is occluded.
         forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
     }
 
@@ -222,34 +223,34 @@
             return;
         }
 
-        final SizeCompatUILayout layout = createLayout(context, displayId, taskId, taskConfig,
-                taskListener);
-        mActiveLayouts.put(taskId, layout);
-        layout.createSizeCompatButton(showOnDisplay(displayId));
+        final CompatUIWindowManager compatUIWindowManager =
+                createLayout(context, displayId, taskId, taskConfig, taskListener);
+        mActiveLayouts.put(taskId, compatUIWindowManager);
+        compatUIWindowManager.createLayout(showOnDisplay(displayId));
     }
 
     @VisibleForTesting
-    SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+    CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
             Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
-        final SizeCompatUILayout layout = new SizeCompatUILayout(mSyncQueue, mCallback, context,
-                taskConfig, taskId, taskListener, mDisplayController.getDisplayLayout(displayId),
-                mHasShownHint);
+        final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
+                taskConfig, mSyncQueue, mCallback, taskId, taskListener,
+                mDisplayController.getDisplayLayout(displayId), mHasShownHint);
         // Only show hint for the first time.
         mHasShownHint = true;
-        return layout;
+        return compatUIWindowManager;
     }
 
     private void updateLayout(int taskId, Configuration taskConfig,
             ShellTaskOrganizer.TaskListener taskListener) {
-        final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+        final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
         if (layout == null) {
             return;
         }
-        layout.updateSizeCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
+        layout.updateCompatInfo(taskConfig, taskListener, showOnDisplay(layout.getDisplayId()));
     }
 
     private void removeLayout(int taskId) {
-        final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+        final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
         if (layout != null) {
             layout.release();
             mActiveLayouts.remove(taskId);
@@ -275,19 +276,19 @@
         return context;
     }
 
-    private void forAllLayoutsOnDisplay(int displayId, Consumer<SizeCompatUILayout> callback) {
+    private void forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManager> callback) {
         forAllLayouts(layout -> layout.getDisplayId() == displayId, callback);
     }
 
-    private void forAllLayouts(Consumer<SizeCompatUILayout> callback) {
+    private void forAllLayouts(Consumer<CompatUIWindowManager> callback) {
         forAllLayouts(layout -> true, callback);
     }
 
-    private void forAllLayouts(Predicate<SizeCompatUILayout> condition,
-            Consumer<SizeCompatUILayout> callback) {
+    private void forAllLayouts(Predicate<CompatUIWindowManager> condition,
+            Consumer<CompatUIWindowManager> callback) {
         for (int i = 0; i < mActiveLayouts.size(); i++) {
             final int taskId = mActiveLayouts.keyAt(i);
-            final SizeCompatUILayout layout = mActiveLayouts.get(taskId);
+            final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
             if (layout != null && condition.test(layout)) {
                 callback.accept(layout);
             }
@@ -298,11 +299,11 @@
      * The interface for calls from outside the Shell, within the host process.
      */
     @ExternalThread
-    private class SizeCompatUIImpl implements SizeCompatUI {
+    private class CompatUIImpl implements CompatUI {
         @Override
         public void onKeyguardOccludedChanged(boolean occluded) {
             mMainExecutor.execute(() -> {
-                SizeCompatUIController.this.onKeyguardOccludedChanged(occluded);
+                CompatUIController.this.onKeyguardOccludedChanged(occluded);
             });
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
new file mode 100644
index 0000000..ea4f209
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -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.wm.shell.compatui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.wm.shell.R;
+
+/**
+ * Container for compat UI controls.
+ */
+public class CompatUILayout extends LinearLayout {
+
+    private CompatUIWindowManager mWindowManager;
+
+    public CompatUILayout(Context context) {
+        this(context, null);
+    }
+
+    public CompatUILayout(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public CompatUILayout(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public CompatUILayout(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    void inject(CompatUIWindowManager windowManager) {
+        mWindowManager = windowManager;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        // Need to relayout after changes like hiding / showing a hint since they affect size.
+        // Doing this directly in setSizeCompatHintVisibility can result in flaky animation.
+        mWindowManager.relayout();
+    }
+
+    void setSizeCompatHintVisibility(boolean show) {
+        final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
+        int visibility = show ? View.VISIBLE : View.GONE;
+        if (sizeCompatHint.getVisibility() == visibility) {
+            return;
+        }
+        sizeCompatHint.setVisibility(visibility);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
+        restartButton.setOnClickListener(view -> mWindowManager.onRestartButtonClicked());
+        restartButton.setOnLongClickListener(view -> {
+            mWindowManager.onRestartButtonLongClicked();
+            return true;
+        });
+
+        final LinearLayout sizeCompatHint = findViewById(R.id.size_compat_hint);
+        ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
+                .setText(R.string.restart_button_description);
+        sizeCompatHint.setOnClickListener(view -> setSizeCompatHintVisibility(/* show= */ false));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
new file mode 100644
index 0000000..997ad04
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.util.Log;
+import android.view.IWindow;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Holds view hierarchy of a root surface and helps to inflate and manage layout for compat
+ * controls.
+ */
+class CompatUIWindowManager extends WindowlessWindowManager {
+
+    private static final String TAG = "CompatUIWindowManager";
+
+    private final SyncTransactionQueue mSyncQueue;
+    private final CompatUIController.CompatUICallback mCallback;
+    private final int mDisplayId;
+    private final int mTaskId;
+    private final Rect mStableBounds;
+
+    private Context mContext;
+    private Configuration mTaskConfig;
+    private ShellTaskOrganizer.TaskListener mTaskListener;
+    private DisplayLayout mDisplayLayout;
+
+    @VisibleForTesting
+    boolean mShouldShowHint;
+
+    @Nullable
+    @VisibleForTesting
+    CompatUILayout mCompatUILayout;
+
+    @Nullable
+    private SurfaceControlViewHost mViewHost;
+    @Nullable
+    private SurfaceControl mLeash;
+
+    CompatUIWindowManager(Context context, Configuration taskConfig,
+            SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
+            int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
+            boolean hasShownHint) {
+        super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
+        mContext = context;
+        mSyncQueue = syncQueue;
+        mCallback = callback;
+        mTaskConfig = taskConfig;
+        mDisplayId = mContext.getDisplayId();
+        mTaskId = taskId;
+        mTaskListener = taskListener;
+        mDisplayLayout = displayLayout;
+        mShouldShowHint = !hasShownHint;
+        mStableBounds = new Rect();
+        mDisplayLayout.getStableBounds(mStableBounds);
+    }
+
+    @Override
+    public void setConfiguration(Configuration configuration) {
+        super.setConfiguration(configuration);
+        mContext = mContext.createConfigurationContext(configuration);
+    }
+
+    @Override
+    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
+        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
+        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                .setContainerLayer()
+                .setName("CompatUILeash")
+                .setHidden(false)
+                .setCallsite("CompatUIWindowManager#attachToParentSurface");
+        attachToParentSurface(builder);
+        mLeash = builder.build();
+        b.setParent(mLeash);
+    }
+
+    /** Creates the layout for compat controls. */
+    void createLayout(boolean show) {
+        if (!show || mCompatUILayout != null) {
+            // Wait until compat controls should be visible.
+            return;
+        }
+
+        initCompatUi();
+        updateSurfacePosition();
+
+        mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
+    }
+
+    /** Called when compat info changed. */
+    void updateCompatInfo(Configuration taskConfig,
+            ShellTaskOrganizer.TaskListener taskListener, boolean show) {
+        final Configuration prevTaskConfig = mTaskConfig;
+        final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
+        mTaskConfig = taskConfig;
+        mTaskListener = taskListener;
+
+        // Update configuration.
+        mContext = mContext.createConfigurationContext(taskConfig);
+        setConfiguration(taskConfig);
+
+        if (mCompatUILayout == null || prevTaskListener != taskListener) {
+            // TaskListener changed, recreate the layout for new surface parent.
+            release();
+            createLayout(show);
+            return;
+        }
+
+        if (!taskConfig.windowConfiguration.getBounds()
+                .equals(prevTaskConfig.windowConfiguration.getBounds())) {
+            // Reposition the UI surfaces.
+            updateSurfacePosition();
+        }
+
+        if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
+            // Update layout for RTL.
+            mCompatUILayout.setLayoutDirection(taskConfig.getLayoutDirection());
+            updateSurfacePosition();
+        }
+    }
+
+    /** Called when the visibility of the UI should change. */
+    void updateVisibility(boolean show) {
+        if (mCompatUILayout == null) {
+            // Layout may not have been created because it was hidden previously.
+            createLayout(show);
+            return;
+        }
+
+        // Hide compat UIs when IME is showing.
+        final int newVisibility = show ? View.VISIBLE : View.GONE;
+        if (mCompatUILayout.getVisibility() != newVisibility) {
+            mCompatUILayout.setVisibility(newVisibility);
+        }
+    }
+
+    /** Called when display layout changed. */
+    void updateDisplayLayout(DisplayLayout displayLayout) {
+        final Rect prevStableBounds = mStableBounds;
+        final Rect curStableBounds = new Rect();
+        displayLayout.getStableBounds(curStableBounds);
+        mDisplayLayout = displayLayout;
+        if (!prevStableBounds.equals(curStableBounds)) {
+            // Stable bounds changed, update UI surface positions.
+            updateSurfacePosition();
+            mStableBounds.set(curStableBounds);
+        }
+    }
+
+    /** Called when it is ready to be placed compat UI surface. */
+    void attachToParentSurface(SurfaceControl.Builder b) {
+        mTaskListener.attachChildSurfaceToTask(mTaskId, b);
+    }
+
+    /** Called when the restart button is clicked. */
+    void onRestartButtonClicked() {
+        mCallback.onSizeCompatRestartButtonClicked(mTaskId);
+    }
+
+    /** Called when the restart button is long clicked. */
+    void onRestartButtonLongClicked() {
+        if (mCompatUILayout == null) {
+            return;
+        }
+        mCompatUILayout.setSizeCompatHintVisibility(/* show= */ true);
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    int getTaskId() {
+        return mTaskId;
+    }
+
+    /** Releases the surface control and tears down the view hierarchy. */
+    void release() {
+        mCompatUILayout = null;
+
+        if (mViewHost != null) {
+            mViewHost.release();
+            mViewHost = null;
+        }
+
+        if (mLeash != null) {
+            final SurfaceControl leash = mLeash;
+            mSyncQueue.runInSync(t -> t.remove(leash));
+            mLeash = null;
+        }
+    }
+
+    void relayout() {
+        mViewHost.relayout(getWindowLayoutParams());
+        updateSurfacePosition();
+    }
+
+    @VisibleForTesting
+    void updateSurfacePosition() {
+        if (mCompatUILayout == null || mLeash == null) {
+            return;
+        }
+
+        // Use stable bounds to prevent controls from overlapping with system bars.
+        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
+        final Rect stableBounds = new Rect();
+        mDisplayLayout.getStableBounds(stableBounds);
+        stableBounds.intersect(taskBounds);
+
+        // Position of the button in the container coordinate.
+        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+                ? stableBounds.left - taskBounds.left
+                : stableBounds.right - taskBounds.left - mCompatUILayout.getMeasuredWidth();
+        final int positionY = stableBounds.bottom - taskBounds.top
+                - mCompatUILayout.getMeasuredHeight();
+
+        updateSurfacePosition(positionX, positionY);
+    }
+
+    private int getLayoutDirection() {
+        return mContext.getResources().getConfiguration().getLayoutDirection();
+    }
+
+    private void updateSurfacePosition(int positionX, int positionY) {
+        mSyncQueue.runInSync(t -> {
+            if (mLeash == null || !mLeash.isValid()) {
+                Log.w(TAG, "The leash has been released.");
+                return;
+            }
+            t.setPosition(mLeash, positionX, positionY);
+            // The compat UI should be the topmost child of the Task in case there can be more
+            // than one children.
+            t.setLayer(mLeash, Integer.MAX_VALUE);
+        });
+    }
+
+    /** Inflates {@link CompatUILayout} on to the root surface. */
+    private void initCompatUi() {
+        if (mViewHost != null) {
+            throw new IllegalStateException(
+                    "A UI has already been created with this window manager.");
+        }
+
+        // Construction extracted into the separate methods to allow injection for tests.
+        mViewHost = createSurfaceViewHost();
+        mCompatUILayout = inflateCompatUILayout();
+        mCompatUILayout.inject(this);
+
+        mCompatUILayout.setSizeCompatHintVisibility(mShouldShowHint);
+
+        mViewHost.setView(mCompatUILayout, getWindowLayoutParams());
+
+        // Only show by default for the first time.
+        mShouldShowHint = false;
+    }
+
+    @VisibleForTesting
+    CompatUILayout inflateCompatUILayout() {
+        return (CompatUILayout) LayoutInflater.from(mContext)
+                .inflate(R.layout.compat_ui_layout, null);
+    }
+
+    @VisibleForTesting
+    SurfaceControlViewHost createSurfaceViewHost() {
+        return new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
+    }
+
+    /** Gets the layout params. */
+    private WindowManager.LayoutParams getWindowLayoutParams() {
+        // Measure how big the hint is since its size depends on the text size.
+        mCompatUILayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
+                // Cannot be wrap_content as this determines the actual window size
+                mCompatUILayout.getMeasuredWidth(), mCompatUILayout.getMeasuredHeight(),
+                TYPE_APPLICATION_OVERLAY,
+                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        winParams.token = new Binder();
+        winParams.setTitle(CompatUILayout.class.getSimpleName() + mTaskId);
+        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
+        return winParams;
+    }
+
+}
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 54ce6bb..23d9b8b 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
@@ -36,6 +36,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.TaskViewFactoryController;
+import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.apppairs.AppPairsController;
@@ -54,6 +55,8 @@
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+import com.android.wm.shell.compatui.CompatUI;
+import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
 import com.android.wm.shell.draganddrop.DragAndDrop;
@@ -75,8 +78,6 @@
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
-import com.android.wm.shell.sizecompatui.SizeCompatUIController;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.startingsurface.StartingSurface;
@@ -173,25 +174,25 @@
     @Provides
     static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
             Context context,
-            SizeCompatUIController sizeCompatUI,
+            CompatUIController compatUI,
             Optional<RecentTasksController> recentTasksOptional
     ) {
-        return new ShellTaskOrganizer(mainExecutor, context, sizeCompatUI, recentTasksOptional);
+        return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional);
     }
 
     @WMSingleton
     @Provides
-    static SizeCompatUI provideSizeCompatUI(SizeCompatUIController sizeCompatUIController) {
-        return sizeCompatUIController.asSizeCompatUI();
+    static CompatUI provideCompatUI(CompatUIController compatUIController) {
+        return compatUIController.asCompatUI();
     }
 
     @WMSingleton
     @Provides
-    static SizeCompatUIController provideSizeCompatUIController(Context context,
+    static CompatUIController provideCompatUIController(Context context,
             DisplayController displayController, DisplayInsetsController displayInsetsController,
             DisplayImeController imeController, SyncTransactionQueue syncQueue,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new SizeCompatUIController(context, displayController, displayInsetsController,
+        return new CompatUIController(context, displayController, displayInsetsController,
                 imeController, syncQueue, mainExecutor);
     }
 
@@ -361,7 +362,6 @@
         return Optional.empty();
     }
 
-
     //
     // Task to Surface communication
     //
@@ -463,6 +463,12 @@
                 animExecutor);
     }
 
+    @WMSingleton
+    @Provides
+    static TaskViewTransitions provideTaskViewTransitions(Transitions transitions) {
+        return new TaskViewTransitions(transitions);
+    }
+
     //
     // Display areas
     //
@@ -594,8 +600,10 @@
     static TaskViewFactoryController provideTaskViewFactoryController(
             ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor,
-            SyncTransactionQueue syncQueue) {
-        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue);
+            SyncTransactionQueue syncQueue,
+            TaskViewTransitions taskViewTransitions) {
+        return new TaskViewFactoryController(shellTaskOrganizer, mainExecutor, syncQueue,
+                taskViewTransitions);
     }
 
     //
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 f562fd9c..d681a77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.apppairs.AppPairsController;
 import com.android.wm.shell.bubbles.BubbleController;
@@ -106,13 +107,16 @@
             UiEventLogger uiEventLogger,
             ShellTaskOrganizer organizer,
             DisplayController displayController,
+            @DynamicOverride Optional<OneHandedController> oneHandedOptional,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler,
+            TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
         return BubbleController.create(context, null /* synchronizer */,
                 floatingContentCoordinator, statusBarService, windowManager,
                 windowManagerShellWrapper, launcherApps, taskStackListener,
-                uiEventLogger, organizer, displayController, mainExecutor, mainHandler, syncQueue);
+                uiEventLogger, organizer, displayController, oneHandedOptional,
+                mainExecutor, mainHandler, taskViewTransitions, syncQueue);
     }
 
     //
@@ -139,12 +143,10 @@
     static OneHandedController provideOneHandedController(Context context,
             WindowManager windowManager, DisplayController displayController,
             DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
-            UiEventLogger uiEventLogger,
-            @ShellMainThread ShellExecutor mainExecutor,
+            UiEventLogger uiEventLogger, @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler) {
-        return OneHandedController.create(context, windowManager,
-                displayController, displayLayout, taskStackListener, uiEventLogger, mainExecutor,
-                mainHandler);
+        return OneHandedController.create(context, windowManager, displayController, displayLayout,
+                taskStackListener, uiEventLogger, mainExecutor, mainHandler);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index b65a2e4..8e6c05d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -151,7 +151,13 @@
                 final Rect rightHitRegion = new Rect();
                 final Rect rightDrawRegion = bottomOrRightBounds;
 
-                displayRegion.splitVertically(leftHitRegion, rightHitRegion);
+                // If we have existing split regions use those bounds, otherwise split it 50/50
+                if (inSplitScreen) {
+                    leftHitRegion.set(topOrLeftBounds);
+                    rightHitRegion.set(bottomOrRightBounds);
+                } else {
+                    displayRegion.splitVertically(leftHitRegion, rightHitRegion);
+                }
 
                 mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
                 mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
@@ -162,8 +168,13 @@
                 final Rect bottomHitRegion = new Rect();
                 final Rect bottomDrawRegion = bottomOrRightBounds;
 
-                displayRegion.splitHorizontally(
-                        topHitRegion, bottomHitRegion);
+                // If we have existing split regions use those bounds, otherwise split it 50/50
+                if (inSplitScreen) {
+                    topHitRegion.set(topOrLeftBounds);
+                    bottomHitRegion.set(bottomOrRightBounds);
+                } else {
+                    displayRegion.splitHorizontally(topHitRegion, bottomHitRegion);
+                }
 
                 mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
                 mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
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 67f9062..fd3be2b 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
@@ -17,7 +17,9 @@
 package com.android.wm.shell.draganddrop;
 
 import static android.app.StatusBarManager.DISABLE_NONE;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
+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 android.animation.Animator;
@@ -32,11 +34,11 @@
 import android.content.res.Configuration;
 import android.graphics.Color;
 import android.graphics.Insets;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
 import android.view.DragEvent;
 import android.view.SurfaceControl;
-import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.widget.LinearLayout;
@@ -73,6 +75,7 @@
     private DropZoneView mDropZoneView2;
 
     private int mDisplayMargin;
+    private int mDividerSize;
     private Insets mInsets = Insets.NONE;
 
     private boolean mIsShowing;
@@ -89,13 +92,15 @@
 
         mDisplayMargin = context.getResources().getDimensionPixelSize(
                 R.dimen.drop_layout_display_margin);
+        mDividerSize = context.getResources().getDimensionPixelSize(
+                R.dimen.split_divider_bar_width);
 
         mDropZoneView1 = new DropZoneView(context);
         mDropZoneView2 = new DropZoneView(context);
-        addView(mDropZoneView1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT));
-        addView(mDropZoneView2, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT));
+        addView(mDropZoneView1, new LinearLayout.LayoutParams(MATCH_PARENT,
+                MATCH_PARENT));
+        addView(mDropZoneView2, new LinearLayout.LayoutParams(MATCH_PARENT,
+                MATCH_PARENT));
         ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
         ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
         updateContainerMargins();
@@ -165,44 +170,82 @@
         mHasDropped = false;
         mCurrentTarget = null;
 
-        List<ActivityManager.RunningTaskInfo> tasks = null;
-        // Figure out the splashscreen info for the existing task(s).
-        try {
-            tasks = ActivityTaskManager.getService().getTasks(2,
-                            false /* filterOnlyVisibleRecents */,
-                            false /* keepIntentExtra */);
-        } catch (RemoteException e) {
-            // don't show an icon / will just use the defaults
-        }
-        if (tasks != null && !tasks.isEmpty()) {
-            ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
-            Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
-            int bgColor1 = getResizingBackgroundColor(taskInfo1);
-
-            boolean alreadyInSplit = mSplitScreenController != null
-                    && mSplitScreenController.isSplitScreenVisible();
-            if (alreadyInSplit && tasks.size() > 1) {
-                ActivityManager.RunningTaskInfo taskInfo2 = tasks.get(1);
-                Drawable icon2 = mIconProvider.getIcon(taskInfo2.topActivityInfo);
-                int bgColor2 = getResizingBackgroundColor(taskInfo2);
-
-                // figure out which task is on which side
-                int splitPosition1 = mSplitScreenController.getSplitPosition(taskInfo1.taskId);
-                boolean isTask1TopOrLeft = splitPosition1 == SPLIT_POSITION_TOP_OR_LEFT;
-                if (isTask1TopOrLeft) {
-                    mDropZoneView1.setAppInfo(bgColor1, icon1);
-                    mDropZoneView2.setAppInfo(bgColor2, icon2);
-                } else {
-                    mDropZoneView2.setAppInfo(bgColor1, icon1);
-                    mDropZoneView1.setAppInfo(bgColor2, icon2);
-                }
-            } else {
+        boolean alreadyInSplit = mSplitScreenController != null
+                && mSplitScreenController.isSplitScreenVisible();
+        if (!alreadyInSplit) {
+            List<ActivityManager.RunningTaskInfo> tasks = null;
+            // Figure out the splashscreen info for the existing task.
+            try {
+                tasks = ActivityTaskManager.getService().getTasks(1,
+                        false /* filterOnlyVisibleRecents */,
+                        false /* keepIntentExtra */);
+            } catch (RemoteException e) {
+                // don't show an icon / will just use the defaults
+            }
+            if (tasks != null && !tasks.isEmpty()) {
+                ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
+                Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
+                int bgColor1 = getResizingBackgroundColor(taskInfo1);
                 mDropZoneView1.setAppInfo(bgColor1, icon1);
                 mDropZoneView2.setAppInfo(bgColor1, icon1);
+                updateDropZoneSizes(null, null); // passing null splits the views evenly
             }
+        } else {
+            // We're already in split so get taskInfo from the controller to populate icon / color.
+            ActivityManager.RunningTaskInfo topOrLeftTask =
+                    mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
+            ActivityManager.RunningTaskInfo bottomOrRightTask =
+                    mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
+            if (topOrLeftTask != null && bottomOrRightTask != null) {
+                Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo);
+                int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask);
+                Drawable bottomOrRightIcon = mIconProvider.getIcon(
+                        bottomOrRightTask.topActivityInfo);
+                int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask);
+                mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
+                mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
+            }
+
+            // Update the dropzones to match existing split sizes
+            Rect topOrLeftBounds = new Rect();
+            Rect bottomOrRightBounds = new Rect();
+            mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
+            updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
         }
     }
 
+    /**
+     * Sets the size of the two drop zones based on the provided bounds. The divider sits between
+     * the views and its size is included in the calculations.
+     *
+     * @param bounds1 bounds to apply to the first dropzone view, null if split in half.
+     * @param bounds2 bounds to apply to the second dropzone view, null if split in half.
+     */
+    private void updateDropZoneSizes(Rect bounds1, Rect bounds2) {
+        final int orientation = getResources().getConfiguration().orientation;
+        final boolean isPortrait = orientation == Configuration.ORIENTATION_PORTRAIT;
+        final int halfDivider = mDividerSize / 2;
+        final LinearLayout.LayoutParams dropZoneView1 =
+                (LayoutParams) mDropZoneView1.getLayoutParams();
+        final LinearLayout.LayoutParams dropZoneView2 =
+                (LayoutParams) mDropZoneView2.getLayoutParams();
+        if (isPortrait) {
+            dropZoneView1.width = MATCH_PARENT;
+            dropZoneView2.width = MATCH_PARENT;
+            dropZoneView1.height = bounds1 != null ? bounds1.height() + halfDivider : MATCH_PARENT;
+            dropZoneView2.height = bounds2 != null ? bounds2.height() + halfDivider : MATCH_PARENT;
+        } else {
+            dropZoneView1.width = bounds1 != null ? bounds1.width() + halfDivider : MATCH_PARENT;
+            dropZoneView2.width = bounds2 != null ? bounds2.width() + halfDivider : MATCH_PARENT;
+            dropZoneView1.height = MATCH_PARENT;
+            dropZoneView2.height = MATCH_PARENT;
+        }
+        dropZoneView1.weight = bounds1 != null ? 0 : 1;
+        dropZoneView2.weight = bounds2 != null ? 0 : 1;
+        mDropZoneView1.setLayoutParams(dropZoneView1);
+        mDropZoneView2.setLayoutParams(dropZoneView2);
+    }
+
     public void show() {
         mIsShowing = true;
         recomputeDropTargets();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 72bb655..507204c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -198,7 +198,7 @@
         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
-        OneHandedState transitionState = new OneHandedState();
+        OneHandedState oneHandedState = new OneHandedState();
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
                 settingsUtil, windowManager);
         OneHandedAnimationController animationController =
@@ -216,7 +216,7 @@
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         return new OneHandedController(context, displayController,
                 oneHandedBackgroundPanelOrganizer, organizer, touchHandler, tutorialHandler,
-                settingsUtil, accessibilityUtil, timeoutHandler, transitionState,
+                settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState,
                 oneHandedUiEventsLogger, overlayManager, taskStackListener, mainExecutor,
                 mainHandler);
     }
@@ -392,8 +392,10 @@
         mEventCallback = callback;
     }
 
-    @VisibleForTesting
-    void registerTransitionCallback(OneHandedTransitionCallback callback) {
+    /**
+     * Registers {@link OneHandedTransitionCallback} to monitor the transition status
+     */
+    public void registerTransitionCallback(OneHandedTransitionCallback callback) {
         mDisplayAreaOrganizer.registerTransitionCallback(callback);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 6d4773b..c0734e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -112,11 +112,17 @@
     default void showPictureInPictureMenu() {}
 
     /**
-     * Called by NavigationBar in order to listen in for PiP bounds change. This is mostly used
-     * for times where the PiP bounds could conflict with SystemUI elements, such as a stashed
-     * PiP and the Back-from-Edge gesture.
+     * Called by NavigationBar and TaskbarDelegate in order to listen in for PiP bounds change. This
+     * is mostly used for times where the PiP bounds could conflict with SystemUI elements, such as
+     * a stashed PiP and the Back-from-Edge gesture.
      */
-    default void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+    default void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
+
+    /**
+     * Remove a callback added previously. This is used when NavigationBar is removed from the
+     * view hierarchy or destroyed.
+     */
+    default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
 
     /**
      * Dump the current state and information if need.
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 e3674dc..b3558ad 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
@@ -38,6 +38,8 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
 
@@ -89,7 +91,7 @@
 
     private @Nullable Runnable mOnMinimalSizeChangeCallback;
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
-    private @Nullable Consumer<Rect> mOnPipExclusionBoundsChangeCallback;
+    private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
 
     public PipBoundsState(@NonNull Context context) {
         mContext = context;
@@ -108,8 +110,8 @@
     /** Set the current PIP bounds. */
     public void setBounds(@NonNull Rect bounds) {
         mBounds.set(bounds);
-        if (mOnPipExclusionBoundsChangeCallback != null) {
-            mOnPipExclusionBoundsChangeCallback.accept(bounds);
+        for (Consumer<Rect> callback : mOnPipExclusionBoundsChangeCallbacks) {
+            callback.accept(bounds);
         }
     }
 
@@ -407,17 +409,25 @@
     }
 
     /**
-     * Set a callback to watch out for PiP bounds. This is mostly used by SystemUI's
+     * Add a callback to watch out for PiP bounds. This is mostly used by SystemUI's
      * Back-gesture handler, to avoid conflicting with PiP when it's stashed.
      */
-    public void setPipExclusionBoundsChangeCallback(
+    public void addPipExclusionBoundsChangeCallback(
             @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
-        mOnPipExclusionBoundsChangeCallback = onPipExclusionBoundsChangeCallback;
-        if (mOnPipExclusionBoundsChangeCallback != null) {
-            mOnPipExclusionBoundsChangeCallback.accept(getBounds());
+        mOnPipExclusionBoundsChangeCallbacks.add(onPipExclusionBoundsChangeCallback);
+        for (Consumer<Rect> callback : mOnPipExclusionBoundsChangeCallbacks) {
+            callback.accept(getBounds());
         }
     }
 
+    /**
+     * Remove a callback that was previously added.
+     */
+    public void removePipExclusionBoundsChangeCallback(
+            @Nullable Consumer<Rect> onPipExclusionBoundsChangeCallback) {
+        mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback);
+    }
+
     /** Source of truth for the current bounds of PIP that may be in motion. */
     public static class MotionBoundsState {
         /** The bounds used when PIP is in motion (e.g. during a drag or animation) */
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 854fc60e..f0b2716 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
@@ -778,8 +778,8 @@
     }
 
     @Override
-    public boolean supportSizeCompatUI() {
-        // PIP doesn't support size compat.
+    public boolean supportCompatUI() {
+        // PIP doesn't support compat.
         return false;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index eb512af..101a55d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -191,6 +191,7 @@
         mSystemWindows.addView(mPipMenuView,
                 getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                 0, SHELL_ROOT_LAYER_PIP);
+        setShellRootAccessibilityWindow();
     }
 
     private void detachPipMenuView() {
@@ -546,6 +547,10 @@
             mListeners.forEach(l -> l.onPipMenuStateChangeFinish(menuState));
         }
         mMenuState = menuState;
+        setShellRootAccessibilityWindow();
+    }
+
+    private void setShellRootAccessibilityWindow() {
         switch (mMenuState) {
             case MENU_STATE_NONE:
                 mSystemWindows.setShellRootAccessibilityWindow(0, SHELL_ROOT_LAYER_PIP, null);
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 79e3444..d3dc915 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
@@ -843,9 +843,16 @@
         }
 
         @Override
-        public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+        public void addPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
             mMainExecutor.execute(() -> {
-                mPipBoundsState.setPipExclusionBoundsChangeCallback(listener);
+                mPipBoundsState.addPipExclusionBoundsChangeCallback(listener);
+            });
+        }
+
+        @Override
+        public void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) {
+            mMainExecutor.execute(() -> {
+                mPipBoundsState.removePipExclusionBoundsChangeCallback(listener);
             });
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 0fbdf90..92a3598 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -120,6 +120,11 @@
         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
         mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
 
+        if (mTargetViewContainer != null) {
+            // init can be called multiple times, remove the old one from view hierarchy first.
+            cleanUpDismissTarget();
+        }
+
         mTargetView = new DismissCircleView(mContext);
         mTargetViewContainer = new FrameLayout(mContext);
         mTargetViewContainer.setBackgroundDrawable(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 82e8273..da4bbe8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -189,7 +189,7 @@
         mEnterSplitButton = findViewById(R.id.enter_split);
         mEnterSplitButton.setAlpha(0);
         mEnterSplitButton.setOnClickListener(v -> {
-            if (mMenuContainer.getAlpha() != 0) {
+            if (mEnterSplitButton.getAlpha() != 0) {
                 enterSplit();
             }
         });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 83390a5..b165706 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -253,9 +253,6 @@
         final Rect newBounds;
         switch (mState) {
             case STATE_PIP_MENU:
-                newBounds = mPipBoundsState.getExpandedBounds();
-                break;
-
             case STATE_PIP:
                 // Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
                 // Internally, it will get the "default" bounds from PipBoundsState and adjust them
@@ -336,11 +333,6 @@
     private void loadConfigurations() {
         final Resources res = mContext.getResources();
         mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
-        // "Cache" bounds for the Pip menu as "expanded" bounds in PipBoundsState. We'll refer back
-        // to this value in resizePinnedStack(), when we are adjusting Pip task/window position for
-        // the menu.
-        mPipBoundsState.setExpandedBounds(
-                Rect.unflattenFromString(res.getString(R.string.pip_menu_bounds)));
     }
 
     private DisplayInfo getDisplayInfo() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
index 6f7cd82..bda685e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuActionButton.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.pip.tv;
 
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
@@ -26,7 +24,6 @@
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
-import android.widget.TextView;
 
 import com.android.wm.shell.R;
 
@@ -36,12 +33,7 @@
  */
 public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
     private final ImageView mIconImageView;
-    private final ImageView mButtonImageView;
-    private final TextView mDescriptionTextView;
-    private Animator mTextFocusGainAnimator;
-    private Animator mButtonFocusGainAnimator;
-    private Animator mTextFocusLossAnimator;
-    private Animator mButtonFocusLossAnimator;
+    private final View mButtonView;
     private OnClickListener mOnClickListener;
 
     public TvPipMenuActionButton(Context context) {
@@ -64,8 +56,7 @@
         inflater.inflate(R.layout.tv_pip_menu_action_button, this);
 
         mIconImageView = findViewById(R.id.icon);
-        mButtonImageView = findViewById(R.id.button);
-        mDescriptionTextView = findViewById(R.id.desc);
+        mButtonView = findViewById(R.id.button);
 
         final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
         final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
@@ -76,43 +67,16 @@
         if (textResId != 0) {
             setTextAndDescription(getContext().getString(textResId));
         }
-
         typedArray.recycle();
     }
 
     @Override
-    public void onFinishInflate() {
-        super.onFinishInflate();
-        mButtonImageView.setOnFocusChangeListener((v, hasFocus) -> {
-            if (hasFocus) {
-                startFocusGainAnimation();
-            } else {
-                startFocusLossAnimation();
-            }
-        });
-
-        mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_gain_animation);
-        mTextFocusGainAnimator.setTarget(mDescriptionTextView);
-        mButtonFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_gain_animation);
-        mButtonFocusGainAnimator.setTarget(mButtonImageView);
-
-        mTextFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_loss_animation);
-        mTextFocusLossAnimator.setTarget(mDescriptionTextView);
-        mButtonFocusLossAnimator = AnimatorInflater.loadAnimator(getContext(),
-                R.anim.tv_pip_controls_focus_loss_animation);
-        mButtonFocusLossAnimator.setTarget(mButtonImageView);
-    }
-
-    @Override
     public void setOnClickListener(OnClickListener listener) {
         // We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to
         // the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
         // listener to the ImageView.
         mOnClickListener = listener;
-        mButtonImageView.setOnClickListener(listener != null ? this : null);
+        mButtonView.setOnClickListener(listener != null ? this : null);
     }
 
     @Override
@@ -143,55 +107,16 @@
      * Sets the text for description the with the given string.
      */
     public void setTextAndDescription(CharSequence text) {
-        mButtonImageView.setContentDescription(text);
-        mDescriptionTextView.setText(text);
+        mButtonView.setContentDescription(text);
     }
 
-    private static void cancelAnimator(Animator animator) {
-        if (animator.isStarted()) {
-            animator.cancel();
-        }
+    @Override
+    public void setEnabled(boolean enabled) {
+        mButtonView.setEnabled(enabled);
     }
 
-    /**
-     * Starts the focus gain animation.
-     */
-    public void startFocusGainAnimation() {
-        cancelAnimator(mButtonFocusLossAnimator);
-        cancelAnimator(mTextFocusLossAnimator);
-        mTextFocusGainAnimator.start();
-        if (mButtonImageView.getAlpha() < 1f) {
-            // If we had faded out the ripple drawable, run our manual focus change animation.
-            // See the comment at {@link #startFocusLossAnimation()} for the reason of manual
-            // animator.
-            mButtonFocusGainAnimator.start();
-        }
-    }
-
-    /**
-     * Starts the focus loss animation.
-     */
-    public void startFocusLossAnimation() {
-        cancelAnimator(mButtonFocusGainAnimator);
-        cancelAnimator(mTextFocusGainAnimator);
-        mTextFocusLossAnimator.start();
-        if (mButtonImageView.hasFocus()) {
-            // Button uses ripple that has the default animation for the focus changes.
-            // However, it doesn't expose the API to fade out while it is focused, so we should
-            // manually run the fade out animation when PIP controls row loses focus.
-            mButtonFocusLossAnimator.start();
-        }
-    }
-
-    /**
-     * Resets to initial state.
-     */
-    public void reset() {
-        cancelAnimator(mButtonFocusGainAnimator);
-        cancelAnimator(mTextFocusGainAnimator);
-        cancelAnimator(mButtonFocusLossAnimator);
-        cancelAnimator(mTextFocusLossAnimator);
-        mButtonImageView.setAlpha(1f);
-        mDescriptionTextView.setAlpha(mButtonImageView.hasFocus() ? 1f : 0f);
+    @Override
+    public boolean isEnabled() {
+        return mButtonView.isEnabled();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ee41b41..77bfa07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ParceledListSlice;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.util.Log;
 import android.view.SurfaceControl;
@@ -122,19 +123,19 @@
         if (DEBUG) Log.d(TAG, "showMenu()");
 
         if (mMenuView != null) {
-            mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
-                    mPipBoundsState.getDisplayBounds().width(),
-                    mPipBoundsState.getDisplayBounds().height()));
+            Rect pipBounds = mPipBoundsState.getBounds();
+            mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(
+                    MENU_WINDOW_TITLE, pipBounds.width(), pipBounds.height()));
             maybeUpdateMenuViewActions();
-            mMenuView.show();
 
-            // By default, SystemWindows views are above everything else.
-            // Set the relative z-order so the menu is below PiP.
-            if (mMenuView.getWindowSurfaceControl() != null && mLeash != null) {
+            SurfaceControl menuSurfaceControl = mSystemWindows.getViewSurface(mMenuView);
+            if (menuSurfaceControl != null) {
                 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-                t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, -1);
+                t.setRelativeLayer(mMenuView.getWindowSurfaceControl(), mLeash, 1);
+                t.setPosition(menuSurfaceControl, pipBounds.left, pipBounds.top);
                 t.apply();
             }
+            mMenuView.show();
         }
     }
 
@@ -181,7 +182,15 @@
 
     private void onMediaActionsChanged(List<RemoteAction> actions) {
         if (DEBUG) Log.d(TAG, "onMediaActionsChanged()");
-        updateAdditionalActionsList(mMediaActions, actions);
+
+        // Hide disabled actions.
+        List<RemoteAction> enabledActions = new ArrayList<>();
+        for (RemoteAction remoteAction : actions) {
+            if (remoteAction.isEnabled()) {
+                enabledActions.add(remoteAction);
+            }
+        }
+        updateAdditionalActionsList(mMediaActions, enabledActions);
     }
 
     private void updateAdditionalActionsList(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index d6cd9ea..4327f15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -24,9 +24,7 @@
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.content.Context;
-import android.graphics.Color;
 import android.os.Handler;
-import android.os.Looper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -55,13 +53,13 @@
     private static final String TAG = "TvPipMenuView";
     private static final boolean DEBUG = TvPipController.DEBUG;
 
-    private static final float DISABLED_ACTION_ALPHA = 0.54f;
-
     private final Animator mFadeInAnimation;
     private final Animator mFadeOutAnimation;
-    @Nullable private Listener mListener;
+    @Nullable
+    private Listener mListener;
 
     private final LinearLayout mActionButtonsContainer;
+    private final View mMenuFrameView;
     private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
 
     public TvPipMenuView(@NonNull Context context) {
@@ -88,11 +86,12 @@
         mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button)
                 .setOnClickListener(this);
 
+        mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
         mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation);
-        mFadeInAnimation.setTarget(mActionButtonsContainer);
+        mFadeInAnimation.setTarget(mMenuFrameView);
 
         mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation);
-        mFadeOutAnimation.setTarget(mActionButtonsContainer);
+        mFadeOutAnimation.setTarget(mMenuFrameView);
     }
 
     void setListener(@Nullable Listener listener) {
@@ -103,7 +102,6 @@
         if (DEBUG) Log.d(TAG, "show()");
 
         mFadeInAnimation.start();
-        setAlpha(1.0f);
         grantWindowFocus(true);
     }
 
@@ -111,12 +109,11 @@
         if (DEBUG) Log.d(TAG, "hide()");
 
         mFadeOutAnimation.start();
-        setAlpha(0.0f);
         grantWindowFocus(false);
     }
 
     boolean isVisible() {
-        return getAlpha() == 1.0f;
+        return mMenuFrameView != null && mMenuFrameView.getAlpha() != 0.0f;
     }
 
     private void grantWindowFocus(boolean grantFocus) {
@@ -140,9 +137,7 @@
             final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
             // Add buttons until we have enough to display all of the actions.
             while (actionsNumber > buttonsNumber) {
-                final TvPipMenuActionButton button = (TvPipMenuActionButton) layoutInflater.inflate(
-                        R.layout.tv_pip_menu_additional_action_button, mActionButtonsContainer,
-                        false);
+                TvPipMenuActionButton button = new TvPipMenuActionButton(mContext);
                 button.setOnClickListener(this);
 
                 mActionButtonsContainer.addView(button);
@@ -168,13 +163,8 @@
             button.setVisibility(View.VISIBLE); // Ensure the button is visible.
             button.setTextAndDescription(action.getContentDescription());
             button.setEnabled(action.isEnabled());
-            button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
             button.setTag(action);
-
-            action.getIcon().loadDrawableAsync(mContext, drawable -> {
-                drawable.setTint(Color.WHITE);
-                button.setImageDrawable(drawable);
-            }, mainHandler);
+            action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
deleted file mode 100644
index ff6f913..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopup.java
+++ /dev/null
@@ -1,66 +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.wm.shell.sizecompatui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.R;
-
-/** Popup to show the hint about the {@link SizeCompatRestartButton}. */
-public class SizeCompatHintPopup extends FrameLayout implements View.OnClickListener {
-
-    private SizeCompatUILayout mLayout;
-
-    public SizeCompatHintPopup(Context context) {
-        super(context);
-    }
-
-    public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public SizeCompatHintPopup(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    void inject(SizeCompatUILayout layout) {
-        mLayout = layout;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        final LinearLayout hintPopup = findViewById(R.id.size_compat_hint_popup);
-        hintPopup.setOnClickListener(this);
-    }
-
-    @Override
-    public void onClick(View v) {
-        mLayout.dismissHint();
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
deleted file mode 100644
index d75fe517..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButton.java
+++ /dev/null
@@ -1,76 +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.wm.shell.sizecompatui;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.wm.shell.R;
-
-/** Button to restart the size compat activity. */
-public class SizeCompatRestartButton extends FrameLayout implements View.OnClickListener,
-        View.OnLongClickListener {
-
-    private SizeCompatUILayout mLayout;
-
-    public SizeCompatRestartButton(@NonNull Context context) {
-        super(context);
-    }
-
-    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public SizeCompatRestartButton(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    void inject(SizeCompatUILayout layout) {
-        mLayout = layout;
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
-        restartButton.setOnClickListener(this);
-        restartButton.setOnLongClickListener(this);
-    }
-
-    @Override
-    public void onClick(View v) {
-        mLayout.onRestartButtonClicked();
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        mLayout.onRestartButtonLongClicked();
-        return true;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
deleted file mode 100644
index c35b89a..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ /dev/null
@@ -1,344 +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.wm.shell.sizecompatui;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Records and handles layout of size compat UI on a task with size compat activity. Helps to
- * calculate proper bounds when configuration or UI position changes.
- */
-class SizeCompatUILayout {
-    private static final String TAG = "SizeCompatUILayout";
-
-    final SyncTransactionQueue mSyncQueue;
-    private final SizeCompatUIController.SizeCompatUICallback mCallback;
-    private Context mContext;
-    private Configuration mTaskConfig;
-    private final int mDisplayId;
-    private final int mTaskId;
-    private ShellTaskOrganizer.TaskListener mTaskListener;
-    private DisplayLayout mDisplayLayout;
-    private final Rect mStableBounds;
-    private final int mButtonWidth;
-    private final int mButtonHeight;
-    private final int mPopupOffsetX;
-    private final int mPopupOffsetY;
-
-    @VisibleForTesting
-    final SizeCompatUIWindowManager mButtonWindowManager;
-    @VisibleForTesting
-    @Nullable
-    SizeCompatUIWindowManager mHintWindowManager;
-    @VisibleForTesting
-    @Nullable
-    SizeCompatRestartButton mButton;
-    @VisibleForTesting
-    @Nullable
-    SizeCompatHintPopup mHint;
-    @VisibleForTesting
-    boolean mShouldShowHint;
-
-    SizeCompatUILayout(SyncTransactionQueue syncQueue,
-            SizeCompatUIController.SizeCompatUICallback callback, Context context,
-            Configuration taskConfig, int taskId, ShellTaskOrganizer.TaskListener taskListener,
-            DisplayLayout displayLayout, boolean hasShownHint) {
-        mSyncQueue = syncQueue;
-        mCallback = callback;
-        mContext = context.createConfigurationContext(taskConfig);
-        mTaskConfig = taskConfig;
-        mDisplayId = mContext.getDisplayId();
-        mTaskId = taskId;
-        mTaskListener = taskListener;
-        mDisplayLayout = displayLayout;
-        mShouldShowHint = !hasShownHint;
-        mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
-
-        mStableBounds = new Rect();
-        mDisplayLayout.getStableBounds(mStableBounds);
-
-        final Resources resources = mContext.getResources();
-        mButtonWidth = resources.getDimensionPixelSize(R.dimen.size_compat_button_width);
-        mButtonHeight = resources.getDimensionPixelSize(R.dimen.size_compat_button_height);
-        mPopupOffsetX = (mButtonWidth / 2) - resources.getDimensionPixelSize(
-                R.dimen.size_compat_hint_corner_radius) - (resources.getDimensionPixelSize(
-                R.dimen.size_compat_hint_point_width) / 2);
-        mPopupOffsetY = mButtonHeight;
-    }
-
-    /** Creates the activity restart button window. */
-    void createSizeCompatButton(boolean show) {
-        if (!show || mButton != null) {
-            // Wait until button should be visible.
-            return;
-        }
-        mButton = mButtonWindowManager.createSizeCompatButton();
-        updateButtonSurfacePosition();
-
-        if (mShouldShowHint) {
-            // Only show by default for the first time.
-            mShouldShowHint = false;
-            createSizeCompatHint();
-        }
-
-        mCallback.onSizeCompatRestartButtonAppeared(mTaskId);
-    }
-
-    /** Creates the restart button hint window. */
-    private void createSizeCompatHint() {
-        if (mHint != null) {
-            // Hint already shown.
-            return;
-        }
-        mHintWindowManager = createHintWindowManager();
-        mHint = mHintWindowManager.createSizeCompatHint();
-        updateHintSurfacePosition();
-    }
-
-    @VisibleForTesting
-    SizeCompatUIWindowManager createHintWindowManager() {
-        return new SizeCompatUIWindowManager(mContext, mTaskConfig, this);
-    }
-
-    /** Dismisses the hint window. */
-    void dismissHint() {
-        mHint = null;
-        if (mHintWindowManager != null) {
-            mHintWindowManager.release();
-            mHintWindowManager = null;
-        }
-    }
-
-    /** Releases the UI windows. */
-    void release() {
-        dismissHint();
-        mButton = null;
-        mButtonWindowManager.release();
-    }
-
-    /** Called when size compat info changed. */
-    void updateSizeCompatInfo(Configuration taskConfig,
-            ShellTaskOrganizer.TaskListener taskListener, boolean show) {
-        final Configuration prevTaskConfig = mTaskConfig;
-        final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
-        mTaskConfig = taskConfig;
-        mTaskListener = taskListener;
-
-        // Update configuration.
-        mContext = mContext.createConfigurationContext(taskConfig);
-        mButtonWindowManager.setConfiguration(taskConfig);
-        if (mHintWindowManager != null) {
-            mHintWindowManager.setConfiguration(taskConfig);
-        }
-
-        if (mButton == null || prevTaskListener != taskListener) {
-            // TaskListener changed, recreate the button for new surface parent.
-            release();
-            createSizeCompatButton(show);
-            return;
-        }
-
-        if (!taskConfig.windowConfiguration.getBounds()
-                .equals(prevTaskConfig.windowConfiguration.getBounds())) {
-            // Reposition the UI surfaces.
-            updateAllSurfacePositions();
-        }
-
-        if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
-            // Update layout for RTL.
-            mButton.setLayoutDirection(taskConfig.getLayoutDirection());
-            updateButtonSurfacePosition();
-            if (mHint != null) {
-                mHint.setLayoutDirection(taskConfig.getLayoutDirection());
-                updateHintSurfacePosition();
-            }
-        }
-    }
-
-    /** Called when display layout changed. */
-    void updateDisplayLayout(DisplayLayout displayLayout) {
-        final Rect prevStableBounds = mStableBounds;
-        final Rect curStableBounds = new Rect();
-        displayLayout.getStableBounds(curStableBounds);
-        mDisplayLayout = displayLayout;
-        if (!prevStableBounds.equals(curStableBounds)) {
-            // Stable bounds changed, update UI surface positions.
-            updateAllSurfacePositions();
-            mStableBounds.set(curStableBounds);
-        }
-    }
-
-    /** Called when the visibility of the UI should change. */
-    void updateVisibility(boolean show) {
-        if (mButton == null) {
-            // Button may not have been created because it was hidden previously.
-            createSizeCompatButton(show);
-            return;
-        }
-
-        // Hide size compat UIs when IME is showing.
-        final int newVisibility = show ? View.VISIBLE : View.GONE;
-        if (mButton.getVisibility() != newVisibility) {
-            mButton.setVisibility(newVisibility);
-        }
-        if (mHint != null && mHint.getVisibility() != newVisibility) {
-            mHint.setVisibility(newVisibility);
-        }
-    }
-
-    /** Gets the layout params for restart button. */
-    WindowManager.LayoutParams getButtonWindowLayoutParams() {
-        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
-                // Cannot be wrap_content as this determines the actual window size
-                mButtonWidth, mButtonHeight,
-                TYPE_APPLICATION_OVERLAY,
-                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
-                PixelFormat.TRANSLUCENT);
-        winParams.token = new Binder();
-        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + getTaskId());
-        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
-        return winParams;
-    }
-
-    /** Gets the layout params for hint popup. */
-    WindowManager.LayoutParams getHintWindowLayoutParams(SizeCompatHintPopup hint) {
-        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
-                // Cannot be wrap_content as this determines the actual window size
-                hint.getMeasuredWidth(), hint.getMeasuredHeight(),
-                TYPE_APPLICATION_OVERLAY,
-                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
-                PixelFormat.TRANSLUCENT);
-        winParams.token = new Binder();
-        winParams.setTitle(SizeCompatHintPopup.class.getSimpleName() + getTaskId());
-        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
-        winParams.windowAnimations = android.R.style.Animation_InputMethod;
-        return winParams;
-    }
-
-    /** Called when it is ready to be placed size compat UI surface. */
-    void attachToParentSurface(SurfaceControl.Builder b) {
-        mTaskListener.attachChildSurfaceToTask(mTaskId, b);
-    }
-
-    /** Called when the restart button is clicked. */
-    void onRestartButtonClicked() {
-        mCallback.onSizeCompatRestartButtonClicked(mTaskId);
-    }
-
-    /** Called when the restart button is long clicked. */
-    void onRestartButtonLongClicked() {
-        createSizeCompatHint();
-    }
-
-    private void updateAllSurfacePositions() {
-        updateButtonSurfacePosition();
-        updateHintSurfacePosition();
-    }
-
-    @VisibleForTesting
-    void updateButtonSurfacePosition() {
-        if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) {
-            return;
-        }
-        final SurfaceControl leash = mButtonWindowManager.getSurfaceControl();
-
-        // Use stable bounds to prevent the button from overlapping with system bars.
-        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
-        final Rect stableBounds = new Rect();
-        mDisplayLayout.getStableBounds(stableBounds);
-        stableBounds.intersect(taskBounds);
-
-        // Position of the button in the container coordinate.
-        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                ? stableBounds.left - taskBounds.left
-                : stableBounds.right - taskBounds.left - mButtonWidth;
-        final int positionY = stableBounds.bottom - taskBounds.top - mButtonHeight;
-
-        updateSurfacePosition(leash, positionX, positionY);
-    }
-
-    @VisibleForTesting
-    void updateHintSurfacePosition() {
-        if (mHint == null || mHintWindowManager == null
-                || mHintWindowManager.getSurfaceControl() == null) {
-            return;
-        }
-        final SurfaceControl leash = mHintWindowManager.getSurfaceControl();
-
-        // Use stable bounds to prevent the hint from overlapping with system bars.
-        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
-        final Rect stableBounds = new Rect();
-        mDisplayLayout.getStableBounds(stableBounds);
-        stableBounds.intersect(taskBounds);
-
-        // Position of the hint in the container coordinate.
-        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                ? stableBounds.left - taskBounds.left + mPopupOffsetX
-                : stableBounds.right - taskBounds.left - mPopupOffsetX - mHint.getMeasuredWidth();
-        final int positionY =
-                stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight();
-
-        updateSurfacePosition(leash, positionX, positionY);
-    }
-
-    private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) {
-        mSyncQueue.runInSync(t -> {
-            if (!leash.isValid()) {
-                Log.w(TAG, "The leash has been released.");
-                return;
-            }
-            t.setPosition(leash, positionX, positionY);
-            // The size compat UI should be the topmost child of the Task in case there can be more
-            // than one children.
-            t.setLayer(leash, Integer.MAX_VALUE);
-        });
-    }
-
-    int getDisplayId() {
-        return mDisplayId;
-    }
-
-    int getTaskId() {
-        return mTaskId;
-    }
-
-    private int getLayoutDirection() {
-        return mContext.getResources().getConfiguration().getLayoutDirection();
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
deleted file mode 100644
index 82f69c3..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ /dev/null
@@ -1,127 +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.wm.shell.sizecompatui;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.view.IWindow;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.SurfaceSession;
-import android.view.View;
-import android.view.WindowlessWindowManager;
-
-import com.android.wm.shell.R;
-
-/**
- * Holds view hierarchy of a root surface and helps to inflate {@link SizeCompatRestartButton} or
- * {@link SizeCompatHintPopup}.
- */
-class SizeCompatUIWindowManager extends WindowlessWindowManager {
-
-    private Context mContext;
-    private final SizeCompatUILayout mLayout;
-
-    @Nullable
-    private SurfaceControlViewHost mViewHost;
-    @Nullable
-    private SurfaceControl mLeash;
-
-    SizeCompatUIWindowManager(Context context, Configuration config, SizeCompatUILayout layout) {
-        super(config, null /* rootSurface */, null /* hostInputToken */);
-        mContext = context;
-        mLayout = layout;
-    }
-
-    @Override
-    public void setConfiguration(Configuration configuration) {
-        super.setConfiguration(configuration);
-        mContext = mContext.createConfigurationContext(configuration);
-    }
-
-    @Override
-    protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
-        // Can't set position for the ViewRootImpl SC directly. Create a leash to manipulate later.
-        final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
-                .setContainerLayer()
-                .setName("SizeCompatUILeash")
-                .setHidden(false)
-                .setCallsite("SizeCompatUIWindowManager#attachToParentSurface");
-        mLayout.attachToParentSurface(builder);
-        mLeash = builder.build();
-        b.setParent(mLeash);
-    }
-
-    /** Inflates {@link SizeCompatRestartButton} on to the root surface. */
-    SizeCompatRestartButton createSizeCompatButton() {
-        if (mViewHost != null) {
-            throw new IllegalStateException(
-                    "A UI has already been created with this window manager.");
-        }
-
-        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
-        final SizeCompatRestartButton button = (SizeCompatRestartButton)
-                LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
-        button.inject(mLayout);
-        mViewHost.setView(button, mLayout.getButtonWindowLayoutParams());
-        return button;
-    }
-
-    /** Inflates {@link SizeCompatHintPopup} on to the root surface. */
-    SizeCompatHintPopup createSizeCompatHint() {
-        if (mViewHost != null) {
-            throw new IllegalStateException(
-                    "A UI has already been created with this window manager.");
-        }
-
-        mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
-        final SizeCompatHintPopup hint = (SizeCompatHintPopup)
-                LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null);
-        // Measure how big the hint is.
-        hint.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-        hint.inject(mLayout);
-        mViewHost.setView(hint, mLayout.getHintWindowLayoutParams(hint));
-        return hint;
-    }
-
-    /** Releases the surface control and tears down the view hierarchy. */
-    void release() {
-        if (mViewHost != null) {
-            mViewHost.release();
-            mViewHost = null;
-        }
-
-        if (mLeash != null) {
-            final SurfaceControl leash = mLeash;
-            mLayout.mSyncQueue.runInSync(t -> t.remove(leash));
-            mLeash = null;
-        }
-    }
-
-    /**
-     * Gets {@link SurfaceControl} of the surface holding size compat UI view. @return {@code null}
-     * if not feasible.
-     */
-    @Nullable
-    SurfaceControl getSurfaceControl() {
-        return mLeash;
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 05552aa..afd5117 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
@@ -102,6 +102,7 @@
     static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
     static final int EXIT_REASON_SCREEN_LOCKED = 7;
     static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
+    static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
     @IntDef(value = {
             EXIT_REASON_UNKNOWN,
             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -112,6 +113,7 @@
             EXIT_REASON_ROOT_TASK_VANISHED,
             EXIT_REASON_SCREEN_LOCKED,
             EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
+            EXIT_REASON_CHILD_TASK_ENTER_PIP,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
@@ -184,6 +186,15 @@
         return mStageCoordinator.isSplitScreenVisible();
     }
 
+    @Nullable
+    public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
+        if (isSplitScreenVisible()) {
+            int taskId = mStageCoordinator.getTaskId(splitPosition);
+            return mTaskOrganizer.getRunningTaskInfo(taskId);
+        }
+        return null;
+    }
+
     public boolean isTaskInSplitScreen(int taskId) {
         return isSplitScreenVisible()
                 && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
@@ -307,8 +318,15 @@
             startIntentLegacy(intent, fillInIntent, position, options);
             return;
         }
-        mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
-                null /* remote */);
+
+        try {
+            options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
+                    null /* wct */);
+            intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */,
+                    null /* requiredPermission */, options);
+        } catch (PendingIntent.CanceledException e) {
+            Slog.e(TAG, "Failed to launch task", e);
+        }
     }
 
     private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
@@ -406,6 +424,8 @@
                 return "APP_FINISHED";
             case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
                 return "APP_DOES_NOT_SUPPORT_MULTIWINDOW";
+            case EXIT_REASON_CHILD_TASK_ENTER_PIP:
+                return "CHILD_TASK_ENTER_PIP";
             default:
                 return "unknown reason, reason int = " + exitReason;
         }
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 86e7b0e..16f4c72 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
@@ -23,6 +23,10 @@
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
 
+import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
 import static com.android.wm.shell.transition.Transitions.isOpeningType;
 
@@ -40,7 +44,9 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.OneShotRemoteHandler;
 import com.android.wm.shell.transition.Transitions;
 
@@ -57,7 +63,7 @@
     private final Transitions mTransitions;
     private final Runnable mOnFinish;
 
-    IBinder mPendingDismiss = null;
+    DismissTransition mPendingDismiss = null;
     IBinder mPendingEnter = null;
 
     private IBinder mAnimatingTransition = null;
@@ -127,12 +133,6 @@
                 }
                 // TODO(shell-transitions): screenshot here
                 final Rect startBounds = new Rect(change.getStartAbsBounds());
-                if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
-                    // Dismissing split via snap which means the still-visible task has been
-                    // dragged to its end position at animation start so reflect that here.
-                    startBounds.offsetTo(change.getEndAbsBounds().left,
-                            change.getEndAbsBounds().top);
-                }
                 final Rect endBounds = new Rect(change.getEndAbsBounds());
                 startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
                 endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
@@ -184,12 +184,20 @@
         return transition;
     }
 
-    /** Starts a transition for dismissing split after dragging the divider to a screen edge */
-    IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
-            @NonNull Transitions.TransitionHandler handler) {
-        final IBinder transition = mTransitions.startTransition(
-                TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
-        mPendingDismiss = transition;
+    /** Starts a transition to dismiss split. */
+    IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
+            Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
+            @SplitScreenController.ExitReason int reason) {
+        final int type = reason == EXIT_REASON_DRAG_DIVIDER
+                ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
+        if (transition == null) {
+            transition = mTransitions.startTransition(type, wct, handler);
+        }
+        mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
+
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+                        + " deduced Dismiss due to %s. toTop=%s",
+                exitReasonToString(reason), stageTypeToString(dismissTop));
         return transition;
     }
 
@@ -206,7 +214,7 @@
         if (mAnimatingTransition == mPendingEnter) {
             mPendingEnter = null;
         }
-        if (mAnimatingTransition == mPendingDismiss) {
+        if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) {
             mPendingDismiss = null;
         }
         mAnimatingTransition = null;
@@ -295,4 +303,20 @@
         mAnimations.add(va);
         mTransitions.getAnimExecutor().execute(va::start);
     }
+
+    /** Bundled information of dismiss transition. */
+    static class DismissTransition {
+        IBinder mTransition;
+
+        int mReason;
+
+        @SplitScreen.StageType
+        int mDismissTop;
+
+        DismissTransition(IBinder transition, int reason, int dismissTop) {
+            this.mTransition = transition;
+            this.mReason = reason;
+            this.mDismissTop = dismissTop;
+        }
+    }
 }
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 e30e6c5..43a1d74b 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
@@ -18,9 +18,10 @@
 
 import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 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.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-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.transitTypeToString;
@@ -32,18 +33,18 @@
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
 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_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;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
 import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
 import static com.android.wm.shell.transition.Transitions.isClosingType;
 import static com.android.wm.shell.transition.Transitions.isOpeningType;
@@ -53,10 +54,8 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
 import android.app.WindowConfiguration;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.Rect;
 import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
@@ -123,9 +122,6 @@
 
     private static final String TAG = StageCoordinator.class.getSimpleName();
 
-    /** internal value for mDismissTop that represents no dismiss */
-    private static final int NO_DISMISS = -2;
-
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
 
     private final MainStage mMainStage;
@@ -158,9 +154,6 @@
     private boolean mKeyguardOccluded;
     private boolean mDeviceSleep;
 
-    @StageType
-    private int mDismissTop = NO_DISMISS;
-
     /** The target stage to dismiss to when unlock after folded. */
     @StageType
     private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
@@ -173,7 +166,6 @@
             setDividerVisibility(false);
             mSplitLayout.resetDividerPosition();
         }
-        mDismissTop = NO_DISMISS;
     };
 
     private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
@@ -341,12 +333,12 @@
         sideOptions = sideOptions != null ? sideOptions : new Bundle();
         setSideStagePosition(sidePosition, wct);
 
+        mSplitLayout.setDivideRatio(splitRatio);
         // Build a request WCT that will launch both apps such that task 0 is on the main stage
         // while task 1 is on the side stage.
         mMainStage.activate(getMainStageBounds(), wct, false /* reparent */);
         mSideStage.setBounds(getSideStageBounds(), wct);
 
-        mSplitLayout.setDivideRatio(splitRatio);
         // Make sure the launch options will put tasks in the corresponding split roots
         addActivityOptions(mainOptions, mMainStage);
         addActivityOptions(sideOptions, mSideStage);
@@ -435,17 +427,6 @@
         mTaskOrganizer.applyTransaction(wct);
     }
 
-    public void startIntent(PendingIntent intent, Intent fillInIntent,
-            @StageType int stage, @SplitPosition int position,
-            @androidx.annotation.Nullable Bundle options,
-            @Nullable RemoteTransition remoteTransition) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        options = resolveStartStage(stage, position, options, wct);
-        wct.sendPendingIntent(intent, fillInIntent, options);
-        mSplitTransitions.startEnterTransition(
-                TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
-    }
-
     /**
      * Collects all the current child tasks of a specific split and prepares transaction to evict
      * them to display.
@@ -474,8 +455,8 @@
                         options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
                     }
                 } else {
-                    // Exit split-screen and launch fullscreen since stage wasn't specified.
-                    prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+                    Slog.w(TAG,
+                            "No stage type nor split position specified to resolve start stage");
                 }
                 break;
             }
@@ -521,6 +502,14 @@
         return SplitLayout.reversePosition(mSideStagePosition);
     }
 
+    int getTaskId(@SplitPosition int splitPosition) {
+        if (mSideStagePosition == splitPosition) {
+            return mSideStage.getTopVisibleChildTaskId();
+        } else {
+            return mMainStage.getTopVisibleChildTaskId();
+        }
+    }
+
     void setSideStagePosition(@SplitPosition int sideStagePosition,
             @Nullable WindowContainerTransaction wct) {
         setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
@@ -629,8 +618,12 @@
         });
         mShouldUpdateRecents = false;
 
-        mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
-        mMainStage.deactivate(wct, childrenToTop == mMainStage);
+        // When the exit split-screen is caused by one of the task enters auto pip,
+        // we want the 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.
+        final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP;
+        mSideStage.removeAllTasks(wct, !fromEnteringPip && childrenToTop == mSideStage);
+        mMainStage.deactivate(wct, !fromEnteringPip && childrenToTop == mMainStage);
         mTaskOrganizer.applyTransaction(wct);
         mSyncQueue.runInSync(t -> t
                 .setWindowCrop(mMainStage.mRootLeash, null)
@@ -660,6 +653,8 @@
             case EXIT_REASON_DRAG_DIVIDER:
             // Either of the split apps have finished
             case EXIT_REASON_APP_FINISHED:
+            // One of the children enters PiP
+            case EXIT_REASON_CHILD_TASK_ENTER_PIP:
                 return true;
             default:
                 return false;
@@ -749,6 +744,11 @@
         }
     }
 
+    private void onStageChildTaskEnterPip(StageListenerImpl stageListener, int taskId) {
+        exitSplitScreen(stageListener == mMainStageListener ? mMainStage : mSideStage,
+                EXIT_REASON_CHILD_TASK_ENTER_PIP);
+    }
+
     private void updateRecentTasksSplitPair() {
         if (!mShouldUpdateRecents) {
             return;
@@ -908,23 +908,22 @@
         }
     }
 
-    @VisibleForTesting
-    IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
-        return mSplitTransitions.startSnapToDismiss(wct, this);
-    }
-
     @Override
     public void onSnappedToDismiss(boolean bottomOrRight) {
         final boolean mainStageToTop =
                 bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
                         : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
-        if (ENABLE_SHELL_TRANSITIONS) {
-            onSnappedToDismissTransition(mainStageToTop);
+
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER);
             return;
         }
-        exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER);
+
+        final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        prepareExitSplitScreen(dismissTop, wct);
+        mSplitTransitions.startDismissTransition(
+                null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
     }
 
     @Override
@@ -965,11 +964,17 @@
 
     private void updateUnfoldBounds() {
         if (mMainUnfoldController != null && mSideUnfoldController != null) {
-            mMainUnfoldController.onLayoutChanged(getMainStageBounds());
-            mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+            mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(),
+                    isLandscape());
+            mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(),
+                    isLandscape());
         }
     }
 
+    private boolean isLandscape() {
+        return mSplitLayout.isLandscape();
+    }
+
     /**
      * Populates `wct` with operations that match the split windows to the current layout.
      * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
@@ -1097,14 +1102,25 @@
             @Nullable TransitionRequestInfo request) {
         final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
         if (triggerTask == null) {
-            // still want to monitor everything while in split-screen, so return non-null.
+            // Still want to monitor everything while in split-screen, so return non-null.
             return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
+        } else if (triggerTask.displayId != mDisplayId) {
+            // Skip handling task on the other display.
+            return null;
         }
 
         WindowContainerTransaction out = null;
         final @WindowManager.TransitionType int type = request.getType();
+        final boolean isOpening = isOpeningType(type);
+        final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+
+        if (isOpening && inFullscreen) {
+            // One task is opening into fullscreen mode, remove the corresponding split record.
+            mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
+        }
+
         if (isSplitScreenVisible()) {
-            // try to handle everything while in split-screen, so return a WCT even if it's empty.
+            // Try to handle everything while in split-screen, so return a WCT even if it's empty.
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  split is active so using split"
                             + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
                             + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
@@ -1112,33 +1128,35 @@
             out = new WindowContainerTransaction();
             final StageTaskListener stage = getStageOfTask(triggerTask);
             if (stage != null) {
-                // dismiss split if the last task in one of the stages is going away
+                // Dismiss split if the last task in one of the stages is going away
                 if (isClosingType(type) && stage.getChildCount() == 1) {
                     // The top should be the opposite side that is closing:
-                    mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
-                            ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
+                    int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE
+                            : STAGE_TYPE_MAIN;
+                    prepareExitSplitScreen(dismissTop, out);
+                    mSplitTransitions.startDismissTransition(transition, out, this, dismissTop,
+                            EXIT_REASON_APP_FINISHED);
                 }
-            } else {
-                if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
-                    // Going home so dismiss both.
-                    mDismissTop = STAGE_TYPE_UNDEFINED;
+            } else if (isOpening && inFullscreen) {
+                final int activityType = triggerTask.getActivityType();
+                if (activityType == ACTIVITY_TYPE_ASSISTANT) {
+                    // We don't want assistant panel to dismiss split screen, so do nothing.
+                } else {
+                    // Going home or occluded by the other fullscreen task, so dismiss both.
+                    prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
+                    final int exitReason = activityType == ACTIVITY_TYPE_HOME
+                            ? EXIT_REASON_RETURN_HOME : EXIT_REASON_UNKNOWN;
+                    mSplitTransitions.startDismissTransition(transition, out, this,
+                            STAGE_TYPE_UNDEFINED, exitReason);
                 }
             }
-            if (mDismissTop != NO_DISMISS) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
-                                + " deduced Dismiss from request. toTop=%s",
-                        stageTypeToString(mDismissTop));
-                prepareExitSplitScreen(mDismissTop, out);
-                mSplitTransitions.mPendingDismiss = transition;
-            }
         } else {
-            // Not in split mode, so look for an open into a split stage just so we can whine and
-            // complain about how this isn't a supported operation.
-            if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
-                if (getStageOfTask(triggerTask) != null) {
-                    throw new IllegalStateException("Entering split implicitly with only one task"
-                            + " isn't supported.");
-                }
+            if (isOpening && getStageOfTask(triggerTask) != null) {
+                // One task is appearing into split, prepare to enter split screen.
+                out = new WindowContainerTransaction();
+                mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */);
+                mSideStage.moveToTop(getSideStageBounds(), out);
+                mSplitTransitions.mPendingEnter = transition;
             }
         }
         return out;
@@ -1150,8 +1168,9 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (transition != mSplitTransitions.mPendingDismiss
-                && transition != mSplitTransitions.mPendingEnter) {
+        if (transition != mSplitTransitions.mPendingEnter && (
+                mSplitTransitions.mPendingDismiss == null
+                        || mSplitTransitions.mPendingDismiss.mTransition != transition)) {
             // Not entering or exiting, so just do some house-keeping and validation.
 
             // If we're not in split-mode, just abort so something else can handle it.
@@ -1194,8 +1213,10 @@
         boolean shouldAnimate = true;
         if (mSplitTransitions.mPendingEnter == transition) {
             shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
-        } else if (mSplitTransitions.mPendingDismiss == transition) {
-            shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
+        } else if (mSplitTransitions.mPendingDismiss != null
+                && mSplitTransitions.mPendingDismiss.mTransition == transition) {
+            shouldAnimate = startPendingDismissAnimation(
+                    mSplitTransitions.mPendingDismiss, info, startTransaction);
         }
         if (!shouldAnimate) return false;
 
@@ -1206,60 +1227,65 @@
 
     private boolean startPendingEnterAnimation(@NonNull IBinder transition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
-        if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
-            // First, verify that we actually have opened 2 apps in split.
-            TransitionInfo.Change mainChild = null;
-            TransitionInfo.Change sideChild = null;
-            for (int iC = 0; iC < info.getChanges().size(); ++iC) {
-                final TransitionInfo.Change change = info.getChanges().get(iC);
-                final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-                if (taskInfo == null || !taskInfo.hasParentTask()) continue;
-                final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
-                if (stageType == STAGE_TYPE_MAIN) {
-                    mainChild = change;
-                } else if (stageType == STAGE_TYPE_SIDE) {
-                    sideChild = change;
-                }
+        // First, verify that we actually have opened apps in both splits.
+        TransitionInfo.Change mainChild = null;
+        TransitionInfo.Change sideChild = null;
+        for (int iC = 0; iC < info.getChanges().size(); ++iC) {
+            final TransitionInfo.Change change = info.getChanges().get(iC);
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            if (taskInfo == null || !taskInfo.hasParentTask()) continue;
+            final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
+            if (stageType == STAGE_TYPE_MAIN) {
+                mainChild = change;
+            } else if (stageType == STAGE_TYPE_SIDE) {
+                sideChild = change;
             }
-            if (mainChild == null || sideChild == null) {
-                throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
-                        + " 2 tasks in transition. Possibly one of them failed to launch");
-                // TODO: fallback logic. Probably start a new transition to exit split before
-                //       applying anything here. Ideally consolidate with transition-merging.
-            }
-
-            // Update local states (before animating).
-            setDividerVisibility(true);
-            setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
-                    null /* wct */);
-            setSplitsVisible(true);
-
-            addDividerBarToTransition(info, t, true /* show */);
-
-            // Make some noise if things aren't totally expected. These states shouldn't effect
-            // transitions locally, but remotes (like Launcher) may get confused if they were
-            // depending on listener callbacks. This can happen because task-organizer callbacks
-            // aren't serialized with transition callbacks.
-            // TODO(b/184679596): Find a way to either include task-org information in
-            //                    the transition, or synchronize task-org callbacks.
-            if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
-                Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
-                        + " to have been called with " + mainChild.getTaskInfo().taskId
-                        + " before startAnimation().");
-            }
-            if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
-                Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
-                        + " to have been called with " + sideChild.getTaskInfo().taskId
-                        + " before startAnimation().");
-            }
-            return true;
-        } else {
-            // TODO: other entry method animations
-            throw new RuntimeException("Unsupported split-entry");
         }
+        if (mainChild == null || sideChild == null) {
+            throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+                    + " 2 tasks in transition. Possibly one of them failed to launch");
+            // TODO: fallback logic. Probably start a new transition to exit split before
+            //       applying anything here. Ideally consolidate with transition-merging.
+        }
+
+        // Update local states (before animating).
+        setDividerVisibility(true);
+        setSplitsVisible(true);
+
+        addDividerBarToTransition(info, t, true /* show */);
+
+        // Make some noise if things aren't totally expected. These states shouldn't effect
+        // transitions locally, but remotes (like Launcher) may get confused if they were
+        // depending on listener callbacks. This can happen because task-organizer callbacks
+        // aren't serialized with transition callbacks.
+        // TODO(b/184679596): Find a way to either include task-org information in
+        //                    the transition, or synchronize task-org callbacks.
+        if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
+            Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+                    + " to have been called with " + mainChild.getTaskInfo().taskId
+                    + " before startAnimation().");
+        }
+        if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
+            Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+                    + " to have been called with " + sideChild.getTaskInfo().taskId
+                    + " before startAnimation().");
+        }
+
+        mShouldUpdateRecents = true;
+        updateRecentTasksSplitPair();
+
+        if (!mLogger.hasStartedSession()) {
+            mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+                    getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+                    getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+                    mSplitLayout.isLandscape());
+        }
+
+        return true;
     }
 
-    private boolean startPendingDismissAnimation(@NonNull IBinder transition,
+    private boolean startPendingDismissAnimation(
+            @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
         // Make some noise if things aren't totally expected. These states shouldn't effect
         // transitions locally, but remotes (like Launcher) may get confused if they were
@@ -1288,6 +1314,21 @@
                     + "] before startAnimation().");
         }
 
+        mRecentTasks.ifPresent(recentTasks -> {
+            // Notify recents if we are exiting in a way that breaks the pair, and disable further
+            // updates to splits in the recents until we enter split again
+            if (shouldBreakPairedTaskInRecents(dismissTransition.mReason) && mShouldUpdateRecents) {
+                for (TransitionInfo.Change change : info.getChanges()) {
+                    final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+                    if (taskInfo != null
+                            && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+                        recentTasks.removeSplitPair(taskInfo.taskId);
+                    }
+                }
+            }
+        });
+        mShouldUpdateRecents = false;
+
         // Update local states.
         setSplitsVisible(false);
         // Wait until after animation to update divider
@@ -1298,15 +1339,17 @@
             t.setWindowCrop(mSideStage.mRootLeash, null);
         }
 
-        if (mDismissTop == STAGE_TYPE_UNDEFINED) {
-            // Going home (dismissing both splits)
-
+        if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
+            logExit(dismissTransition.mReason);
             // TODO: Have a proper remote for this. Until then, though, reset state and use the
             //       normal animation stuff (which falls back to the normal launcher remote).
             t.hide(mSplitLayout.getDividerLeash());
             setDividerVisibility(false);
             mSplitTransitions.mPendingDismiss = null;
             return false;
+        } else {
+            logExitToStage(dismissTransition.mReason,
+                    dismissTransition.mDismissTop == STAGE_TYPE_MAIN);
         }
 
         addDividerBarToTransition(info, t, false /* show */);
@@ -1437,6 +1480,11 @@
         }
 
         @Override
+        public void onChildTaskEnterPip(int taskId) {
+            StageCoordinator.this.onStageChildTaskEnterPip(this, taskId);
+        }
+
+        @Override
         public void onRootTaskVanished() {
             reset();
             StageCoordinator.this.onStageRootTaskVanished(this);
@@ -1445,7 +1493,15 @@
         @Override
         public void onNoLongerSupportMultiWindow() {
             if (mMainStage.isActive()) {
-                StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+                if (!ENABLE_SHELL_TRANSITIONS) {
+                    StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
+                            EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+                }
+
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+                mSplitTransitions.startDismissTransition(null /* transition */, wct,
+                        StageCoordinator.this, STAGE_TYPE_UNDEFINED,
                         EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
             }
         }
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 cd10b9f..2c853c1 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
@@ -20,6 +20,7 @@
 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.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -73,6 +74,8 @@
 
         void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
 
+        void onChildTaskEnterPip(int taskId);
+
         void onRootTaskVanished();
 
         void onNoLongerSupportMultiWindow();
@@ -256,6 +259,9 @@
             mChildrenTaskInfo.remove(taskId);
             mChildrenLeashes.remove(taskId);
             mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
+            if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+                mCallbacks.onChildTaskEnterPip(taskId);
+            }
             if (ENABLE_SHELL_TRANSITIONS) {
                 // Status is managed/synchronized by the transition lifecycle.
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
index e904f6a..0683a25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
@@ -18,11 +18,15 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+
 import android.animation.RectEvaluator;
 import android.animation.TypeEvaluator;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.view.InsetsSource;
@@ -33,6 +37,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
 import com.android.wm.shell.unfold.UnfoldBackgroundController;
@@ -161,12 +166,13 @@
      * Called when split screen stage bounds changed
      * @param bounds new bounds for this stage
      */
-    public void onLayoutChanged(Rect bounds) {
+    public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition,
+            boolean isLandscape) {
         mStageBounds.set(bounds);
 
         for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
             final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-            context.update();
+            context.update(splitPosition, isLandscape);
         }
     }
 
@@ -195,20 +201,27 @@
         final Rect mEndCropRect = new Rect();
         final Rect mCurrentCropRect = new Rect();
 
+        private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED;
+        private boolean mIsLandscape = false;
+
         private AnimationContext(SurfaceControl leash) {
             this.mLeash = leash;
             update();
         }
 
+        private void update(@SplitPosition int splitPosition, boolean isLandscape) {
+            this.mSplitPosition = splitPosition;
+            this.mIsLandscape = isLandscape;
+            update();
+        }
+
         private void update() {
             mStartCropRect.set(mStageBounds);
 
-            if (mTaskbarInsetsSource != null) {
+            boolean taskbarExpanded = isTaskbarExpanded();
+            if (taskbarExpanded) {
                 // Only insets the cropping window with taskbar when taskbar is expanded
-                if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
-                    mStartCropRect.inset(mTaskbarInsetsSource
-                            .calculateVisibleInsets(mStartCropRect));
-                }
+                mStartCropRect.inset(mTaskbarInsetsSource.calculateVisibleInsets(mStartCropRect));
             }
 
             // Offset to surface coordinates as layout bounds are in screen coordinates
@@ -218,7 +231,46 @@
 
             int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
             int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
-            mStartCropRect.inset(margin, margin, margin, margin);
+
+            // Sides adjacent to split bar or task bar are not be animated.
+            Insets margins;
+            if (mIsLandscape) { // Left and right splits.
+                margins = getLandscapeMargins(margin, taskbarExpanded);
+            } else { // Top and bottom splits.
+                margins = getPortraitMargins(margin, taskbarExpanded);
+            }
+            mStartCropRect.inset(margins);
+        }
+
+        private Insets getLandscapeMargins(int margin, boolean taskbarExpanded) {
+            int left = margin;
+            int right = margin;
+            int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin.
+            if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+                right = 0; // Divider margin.
+            } else {
+                left = 0; // Divider margin.
+            }
+            return Insets.of(left, /* top= */ margin, right, bottom);
+        }
+
+        private Insets getPortraitMargins(int margin, boolean taskbarExpanded) {
+            int bottom = margin;
+            int top = margin;
+            if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+                bottom = 0; // Divider margin.
+            } else { // Bottom split.
+                top = 0; // Divider margin.
+                if (taskbarExpanded) {
+                    bottom = 0; // Taskbar margin.
+                }
+            }
+            return Insets.of(/* left= */ margin, top, /* right= */ margin, bottom);
+        }
+
+        private boolean isTaskbarExpanded() {
+            return mTaskbarInsetsSource != null
+                    && mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
index af9a5aa..0183654 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
@@ -127,12 +127,6 @@
                 }
                 // TODO(shell-transitions): screenshot here
                 final Rect startBounds = new Rect(change.getStartAbsBounds());
-                if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
-                    // Dismissing split via snap which means the still-visible task has been
-                    // dragged to its end position at animation start so reflect that here.
-                    startBounds.offsetTo(change.getEndAbsBounds().left,
-                            change.getEndAbsBounds().top);
-                }
                 final Rect endBounds = new Rect(change.getEndAbsBounds());
                 startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
                 endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
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 6643ca1..4ecc0b6 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
@@ -380,9 +380,7 @@
     }
 
     private void drawSizeMatchSnapshot() {
-        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
-                mSnapshot.getHardwareBuffer());
-        mTransaction.setBuffer(mSurfaceControl, graphicBuffer)
+        mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
                 .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
                 .apply();
     }
@@ -428,20 +426,20 @@
         // Scale the mismatch dimensions to fill the task bounds
         mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
         mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
-        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(
-                mSnapshot.getHardwareBuffer());
         mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
-        mTransaction.setBuffer(childSurfaceControl, graphicBuffer);
+        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, background);
+            mTransaction.setBuffer(mSurfaceControl,
+                    HardwareBuffer.createFromGraphicBuffer(background));
         }
         mTransaction.apply();
         childSurfaceControl.release();
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 a0d9d03..a1c0864 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
@@ -313,7 +313,8 @@
                     final int anim = getRotationAnimation(info);
                     if (!(isSeamless || anim == ROTATION_ANIMATION_JUMPCUT)) {
                         mRotationAnimation = new ScreenRotationAnimation(mContext, mSurfaceSession,
-                                mTransactionPool, startTransaction, change, info.getRootLeash());
+                                mTransactionPool, startTransaction, change, info.getRootLeash(),
+                                anim);
                         mRotationAnimation.startAnimation(animations, onAnimFinish,
                                 mTransitionAnimationScaleSetting, mMainExecutor, mAnimExecutor);
                         continue;
@@ -346,6 +347,15 @@
             }
 
             if (change.getMode() == TRANSIT_CHANGE) {
+                // If task is child task, only set position in parent.
+                if (isTask && change.getParent() != null
+                        && info.getChange(change.getParent()).getTaskInfo() != null) {
+                    final Point positionInParent = change.getTaskInfo().positionInParent;
+                    startTransaction.setPosition(change.getLeash(),
+                            positionInParent.x, positionInParent.y);
+                    continue;
+                }
+
                 // No default animation for this, so just update bounds/position.
                 startTransaction.setPosition(change.getLeash(),
                         change.getEndAbsBounds().left - change.getEndRelOffset().x,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 45d8be1..46f73fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -19,6 +19,8 @@
 import static android.hardware.HardwareBuffer.RGBA_8888;
 import static android.hardware.HardwareBuffer.USAGE_PROTECTED_CONTENT;
 import static android.util.RotationUtils.deltaRotation;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
 import static android.view.WindowManagerPolicyConstants.SCREEN_FREEZE_LAYER_BASE;
 
 import static com.android.wm.shell.transition.DefaultTransitionHandler.startSurfaceAnimation;
@@ -59,7 +61,7 @@
  * This class handles the rotation animation when the device is rotated.
  *
  * <p>
- * The screen rotation animation is composed of 4 different part:
+ * The screen rotation animation is composed of 3 different part:
  * <ul>
  * <li> The screenshot: <p>
  *     A screenshot of the whole screen prior the change of orientation is taken to hide the
@@ -75,10 +77,6 @@
  *      To have the animation seem more seamless, we add a color transitioning background behind the
  *      exiting and entering layouts. We compute the brightness of the start and end
  *      layouts and transition from the two brightness values as grayscale underneath the animation
- *
- * <li> The entering Blackframe: <p>
- *     The enter Blackframe is similar to the exit Blackframe but is only used when a custom
- *     rotation animation is used and matches the new content size instead of the screenshot.
  * </ul>
  */
 class ScreenRotationAnimation {
@@ -94,6 +92,7 @@
     private final Rect mStartBounds = new Rect();
     private final Rect mEndBounds = new Rect();
 
+    private final int mAnimHint;
     private final int mStartWidth;
     private final int mStartHeight;
     private final int mEndWidth;
@@ -117,6 +116,7 @@
     // rotations.
     private Animation mRotateExitAnimation;
     private Animation mRotateEnterAnimation;
+    private Animation mRotateAlphaAnimation;
 
     /** Intensity of light/whiteness of the layout before rotation occurs. */
     private float mStartLuma;
@@ -124,9 +124,10 @@
     private float mEndLuma;
 
     ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
-            Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash) {
+            Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
         mContext = context;
         mTransactionPool = pool;
+        mAnimHint = animHint;
 
         mSurfaceControl = change.getLeash();
         mStartWidth = change.getStartAbsBounds().width();
@@ -160,13 +161,6 @@
                 return;
             }
 
-            mBackColorSurface = new SurfaceControl.Builder(session)
-                    .setParent(rootLeash)
-                    .setColorLayer()
-                    .setCallsite("ShellRotationAnimation")
-                    .setName("BackColorSurface")
-                    .build();
-
             mScreenshotLayer = new SurfaceControl.Builder(session)
                     .setParent(mAnimLeash)
                     .setBLASTLayer()
@@ -175,17 +169,9 @@
                     .setName("RotationLayer")
                     .build();
 
-            HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-            mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
-
             GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
                     screenshotBuffer.getHardwareBuffer());
 
-            t.setLayer(mBackColorSurface, -1);
-            t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
-            t.setAlpha(mBackColorSurface, 1);
-            t.show(mBackColorSurface);
-
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
             t.setPosition(mAnimLeash, 0, 0);
             t.setAlpha(mAnimLeash, 1);
@@ -195,6 +181,23 @@
             t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
             t.show(mScreenshotLayer);
 
+            if (!isCustomRotate()) {
+                mBackColorSurface = new SurfaceControl.Builder(session)
+                        .setParent(rootLeash)
+                        .setColorLayer()
+                        .setCallsite("ShellRotationAnimation")
+                        .setName("BackColorSurface")
+                        .build();
+
+                HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+                mStartLuma = getMedianBorderLuma(hardwareBuffer, screenshotBuffer.getColorSpace());
+
+                t.setLayer(mBackColorSurface, -1);
+                t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
+                t.setAlpha(mBackColorSurface, 1);
+                t.show(mBackColorSurface);
+            }
+
         } catch (Surface.OutOfResourcesException e) {
             Slog.w(TAG, "Unable to allocate freeze surface", e);
         }
@@ -203,6 +206,10 @@
         t.apply();
     }
 
+    private boolean isCustomRotate() {
+        return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
+    }
+
     private void setRotation(SurfaceControl.Transaction t) {
         // Compute the transformation matrix that must be applied
         // to the snapshot to make it stay in the same original position
@@ -244,33 +251,44 @@
         // color frame animation.
         //mEndLuma = getLumaOfSurfaceControl(mEndBounds, mSurfaceControl);
 
-        // Figure out how the screen has moved from the original rotation.
-        int delta = deltaRotation(mEndRotation, mStartRotation);
-        switch (delta) { /* Counter-Clockwise Rotations */
-            case Surface.ROTATION_0:
-                mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_0_exit);
-                mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.rotation_animation_enter);
-                break;
-            case Surface.ROTATION_90:
-                mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_plus_90_exit);
-                mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_plus_90_enter);
-                break;
-            case Surface.ROTATION_180:
-                mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_180_exit);
-                mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_180_enter);
-                break;
-            case Surface.ROTATION_270:
-                mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_minus_90_exit);
-                mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                        R.anim.screen_rotate_minus_90_enter);
-                break;
+        final boolean customRotate = isCustomRotate();
+        if (customRotate) {
+            mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                    mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
+                            : R.anim.rotation_animation_xfade_exit);
+            mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                    R.anim.rotation_animation_enter);
+            mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
+                    R.anim.screen_rotate_alpha);
+        } else {
+            // Figure out how the screen has moved from the original rotation.
+            int delta = deltaRotation(mEndRotation, mStartRotation);
+            switch (delta) { /* Counter-Clockwise Rotations */
+                case Surface.ROTATION_0:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_0_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.rotation_animation_enter);
+                    break;
+                case Surface.ROTATION_90:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_plus_90_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_plus_90_enter);
+                    break;
+                case Surface.ROTATION_180:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_180_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_180_enter);
+                    break;
+                case Surface.ROTATION_270:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_minus_90_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            R.anim.screen_rotate_minus_90_enter);
+                    break;
+            }
         }
 
         mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
@@ -281,9 +299,20 @@
         mRotateEnterAnimation.scaleCurrentDuration(animationScale);
 
         mTransaction = mTransactionPool.acquire();
-        startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
-        startScreenshotRotationAnimation(animations, finishCallback, mainExecutor, animExecutor);
-        //startColorAnimation(mTransaction, animationScale);
+        if (customRotate) {
+            mRotateAlphaAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
+            mRotateAlphaAnimation.restrictDuration(MAX_ANIMATION_DURATION);
+            mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
+
+            startScreenshotAlphaAnimation(animations, finishCallback, mainExecutor,
+                    animExecutor);
+            startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+        } else {
+            startDisplayRotation(animations, finishCallback, mainExecutor, animExecutor);
+            startScreenshotRotationAnimation(animations, finishCallback, mainExecutor,
+                    animExecutor);
+            //startColorAnimation(mTransaction, animationScale);
+        }
 
         return true;
     }
@@ -304,6 +333,14 @@
                 0 /* cornerRadius */, null /* clipRect */);
     }
 
+    private void startScreenshotAlphaAnimation(@NonNull ArrayList<Animator> animations,
+            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor,
+            @NonNull ShellExecutor animExecutor) {
+        startSurfaceAnimation(animations, mRotateAlphaAnimation, mAnimLeash, finishCallback,
+                mTransactionPool, mainExecutor, animExecutor, null /* position */,
+                0 /* cornerRadius */, null /* clipRect */);
+    }
+
     private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
         int colorTransitionMs = mContext.getResources().getInteger(
                 R.integer.config_screen_rotation_color_transition);
@@ -351,13 +388,12 @@
                 t.remove(mScreenshotLayer);
             }
             mScreenshotLayer = null;
-
-            if (mBackColorSurface != null) {
-                if (mBackColorSurface.isValid()) {
-                    t.remove(mBackColorSurface);
-                }
-                mBackColorSurface = null;
+        }
+        if (mBackColorSurface != null) {
+            if (mBackColorSurface.isValid()) {
+                t.remove(mBackColorSurface);
             }
+            mBackColorSurface = null;
         }
         t.apply();
         mTransactionPool.release(t);
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 804e449..b8cbfd9 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
@@ -89,12 +89,16 @@
     /** Transition type for entering split by opening an app into side-stage. */
     public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
 
+    /** Transition type for dismissing split-screen. */
+    public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 6;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
     private final ShellExecutor mAnimExecutor;
     private final TransitionPlayerImpl mPlayerImpl;
     private final RemoteTransitionHandler mRemoteTransitionHandler;
+    private final DisplayController mDisplayController;
     private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
 
     /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -122,6 +126,7 @@
         mContext = context;
         mMainExecutor = mainExecutor;
         mAnimExecutor = animExecutor;
+        mDisplayController = displayController;
         mPlayerImpl = new TransitionPlayerImpl();
         // The very last handler (0 in the list) should be the default one.
         mHandlers.add(new DefaultTransitionHandler(displayController, pool, context, mainExecutor,
@@ -147,6 +152,7 @@
         mContext = null;
         mMainExecutor = null;
         mAnimExecutor = null;
+        mDisplayController = null;
         mPlayerImpl = null;
         mRemoteTransitionHandler = null;
     }
@@ -547,6 +553,17 @@
                 break;
             }
         }
+        if (request.getDisplayChange() != null) {
+            TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
+            if (change.getEndRotation() != change.getStartRotation()) {
+                // Is a rotation, so dispatch to all displayChange listeners
+                if (wct == null) {
+                    wct = new WindowContainerTransaction();
+                }
+                mDisplayController.getChangeController().dispatchOnRotateDisplay(wct,
+                        change.getDisplayId(), change.getStartRotation(), change.getEndRotation());
+            }
+        }
         active.mToken = mOrganizer.startTransition(
                 request.getType(), transitionToken, wct);
         mActiveTransitions.add(active);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 1a3e42c..20a9475 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.apppairs
 
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -68,7 +67,7 @@
     @Test
     override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerRotatesScales() {
         // This test doesn't work in shell transitions because of b/206753786
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 3210976..3121218 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
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
@@ -31,7 +30,6 @@
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
 import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
-import org.junit.Assert.assertEquals
 import org.junit.Assume.assumeFalse
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -80,13 +78,42 @@
                 pipApp.launchViaIntent(wmHelper)
                 wmHelper.waitForFullScreenApp(pipApp.component)
                 wmHelper.waitForRotation(Surface.ROTATION_90)
-                assertEquals(Surface.ROTATION_90, device.displayRotation)
             }
         }
 
+    @Presubmit
+    @Test
+    fun displayEndsAt90Degrees() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
+        testSpec.assertWmEnd {
+            hasRotation(Surface.ROTATION_90)
+        }
+    }
+
+    @Presubmit
+    @Test
+    override fun navBarLayerIsVisible() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
+        super.navBarLayerIsVisible()
+    }
+
+    @Presubmit
+    @Test
+    override fun statusBarLayerIsVisible() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
+        super.statusBarLayerIsVisible()
+    }
+
     @FlakyTest
     @Test
-    override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
+    override fun navBarLayerRotatesAndScales() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
+        super.navBarLayerRotatesAndScales()
+    }
 
     @Presubmit
     @Test
@@ -96,9 +123,11 @@
         super.statusBarLayerRotatesScales()
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipWindowInsideDisplay() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWmStart {
             frameRegion(pipApp.component).coversAtMost(startingBounds)
         }
@@ -107,14 +136,18 @@
     @Presubmit
     @Test
     fun pipAppShowsOnTop() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWmEnd {
             isAppWindowOnTop(pipApp.component)
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerInsideDisplay() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersStart {
             visibleRegion(pipApp.component).coversAtMost(startingBounds)
         }
@@ -122,13 +155,19 @@
 
     @Presubmit
     @Test
-    fun pipAlwaysVisible() = testSpec.assertWm {
-        this.isAppWindowVisible(pipApp.component)
+    fun pipAlwaysVisible() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
+        testSpec.assertWm {
+            this.isAppWindowVisible(pipApp.component)
+        }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipAppLayerCoversFullScreen() {
+        // This test doesn't work in shell transitions because of b/208576418
+        assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersEnd {
             visibleRegion(pipApp.component).coversExactly(endingBounds)
         }
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 1fcbf14..a3b98a8f 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
@@ -56,7 +56,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.sizecompatui.SizeCompatUIController;
+import com.android.wm.shell.compatui.CompatUIController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -82,7 +82,7 @@
     @Mock
     private Context mContext;
     @Mock
-    private SizeCompatUIController mSizeCompatUI;
+    private CompatUIController mCompatUI;
 
     ShellTaskOrganizer mOrganizer;
     private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -132,7 +132,7 @@
                     .when(mTaskOrganizerController).registerTaskOrganizer(any());
         } catch (RemoteException e) {}
         mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
-                mSizeCompatUI, Optional.empty()));
+                mCompatUI, Optional.empty()));
     }
 
     @Test
@@ -334,34 +334,34 @@
         mOrganizer.onTaskAppeared(taskInfo1, null);
 
         // sizeCompatActivity is null if top activity is not in size compat.
-        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
                 null /* taskConfig */, null /* taskListener */);
 
         // sizeCompatActivity is non-null if top activity is in size compat.
-        clearInvocations(mSizeCompatUI);
+        clearInvocations(mCompatUI);
         final RunningTaskInfo taskInfo2 =
                 createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
         taskInfo2.displayId = taskInfo1.displayId;
         taskInfo2.topActivityInSizeCompat = true;
         taskInfo2.isVisible = true;
         mOrganizer.onTaskInfoChanged(taskInfo2);
-        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
                 taskInfo1.configuration, taskListener);
 
         // Not show size compat UI if task is not visible.
-        clearInvocations(mSizeCompatUI);
+        clearInvocations(mCompatUI);
         final RunningTaskInfo taskInfo3 =
                 createTaskInfo(taskInfo1.taskId, taskInfo1.getWindowingMode());
         taskInfo3.displayId = taskInfo1.displayId;
         taskInfo3.topActivityInSizeCompat = true;
         taskInfo3.isVisible = false;
         mOrganizer.onTaskInfoChanged(taskInfo3);
-        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
                 null /* taskConfig */, null /* taskListener */);
 
-        clearInvocations(mSizeCompatUI);
+        clearInvocations(mCompatUI);
         mOrganizer.onTaskVanished(taskInfo1);
-        verify(mSizeCompatUI).onSizeCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
+        verify(mCompatUI).onCompatInfoChanged(taskInfo1.displayId, taskInfo1.taskId,
                 null /* taskConfig */, null /* taskListener */);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 1cbad15..03df92f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -21,6 +21,8 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -43,12 +45,14 @@
 import android.view.SurfaceHolder;
 import android.view.SurfaceSession;
 import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SyncTransactionQueue.TransactionRunnable;
+import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
 import org.junit.Before;
@@ -75,6 +79,8 @@
     HandlerExecutor mExecutor;
     @Mock
     SyncTransactionQueue mSyncQueue;
+    @Mock
+    TaskViewTransitions mTaskViewTransitions;
 
     SurfaceSession mSession;
     SurfaceControl mLeash;
@@ -110,7 +116,7 @@
             return null;
         }).when(mSyncQueue).runInSync(any());
 
-        mTaskView = new TaskView(mContext, mOrganizer, mSyncQueue);
+        mTaskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
         mTaskView.setListener(mExecutor, mViewListener);
     }
 
@@ -123,7 +129,7 @@
 
     @Test
     public void testSetPendingListener_throwsException() {
-        TaskView taskView = new TaskView(mContext, mOrganizer, mSyncQueue);
+        TaskView taskView = new TaskView(mContext, mOrganizer, mTaskViewTransitions, mSyncQueue);
         taskView.setListener(mExecutor, mViewListener);
         try {
             taskView.setListener(mExecutor, mViewListener);
@@ -144,7 +150,8 @@
     }
 
     @Test
-    public void testOnTaskAppeared_noSurface() {
+    public void testOnTaskAppeared_noSurface_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
 
         verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
@@ -154,7 +161,8 @@
     }
 
     @Test
-    public void testOnTaskAppeared_withSurface() {
+    public void testOnTaskAppeared_withSurface_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
 
@@ -163,7 +171,8 @@
     }
 
     @Test
-    public void testSurfaceCreated_noTask() {
+    public void testSurfaceCreated_noTask_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
         verify(mViewListener).onInitialized();
@@ -172,7 +181,8 @@
     }
 
     @Test
-    public void testSurfaceCreated_withTask() {
+    public void testSurfaceCreated_withTask_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
 
@@ -181,7 +191,8 @@
     }
 
     @Test
-    public void testSurfaceDestroyed_noTask() {
+    public void testSurfaceDestroyed_noTask_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         SurfaceHolder sh = mock(SurfaceHolder.class);
         mTaskView.surfaceCreated(sh);
         mTaskView.surfaceDestroyed(sh);
@@ -190,7 +201,8 @@
     }
 
     @Test
-    public void testSurfaceDestroyed_withTask() {
+    public void testSurfaceDestroyed_withTask_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         SurfaceHolder sh = mock(SurfaceHolder.class);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(sh);
@@ -201,7 +213,8 @@
     }
 
     @Test
-    public void testOnReleased() {
+    public void testOnReleased_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         mTaskView.release();
@@ -211,7 +224,8 @@
     }
 
     @Test
-    public void testOnTaskVanished() {
+    public void testOnTaskVanished_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.surfaceCreated(mock(SurfaceHolder.class));
         mTaskView.onTaskVanished(mTaskInfo);
@@ -220,7 +234,8 @@
     }
 
     @Test
-    public void testOnBackPressedOnTaskRoot() {
+    public void testOnBackPressedOnTaskRoot_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
 
@@ -228,17 +243,158 @@
     }
 
     @Test
-    public void testSetOnBackPressedOnTaskRoot() {
+    public void testSetOnBackPressedOnTaskRoot_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
     }
 
     @Test
-    public void testUnsetOnBackPressedOnTaskRoot() {
+    public void testUnsetOnBackPressedOnTaskRoot_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         mTaskView.onTaskAppeared(mTaskInfo, mLeash);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
 
         mTaskView.onTaskVanished(mTaskInfo);
         verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
     }
+
+    @Test
+    public void testOnNewTask_noSurface() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+
+        verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+        verify(mViewListener, never()).onInitialized();
+        // If there's no surface the task should be made invisible
+        verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+    }
+
+    @Test
+    public void testSurfaceCreated_noTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        verify(mTaskViewTransitions, never()).setTaskViewVisible(any(), anyBoolean());
+
+        verify(mViewListener).onInitialized();
+        // No task, no visibility change
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testOnNewTask_withSurface() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+
+        verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any());
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testSurfaceCreated_withTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+
+        verify(mViewListener).onInitialized();
+        verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(true));
+
+        mTaskView.prepareOpenAnimation(false /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+
+        verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true));
+    }
+
+    @Test
+    public void testSurfaceDestroyed_noTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        SurfaceHolder sh = mock(SurfaceHolder.class);
+        mTaskView.surfaceCreated(sh);
+        mTaskView.surfaceDestroyed(sh);
+
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testSurfaceDestroyed_withTask() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        SurfaceHolder sh = mock(SurfaceHolder.class);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskView.surfaceCreated(sh);
+        reset(mViewListener);
+        mTaskView.surfaceDestroyed(sh);
+
+        verify(mTaskViewTransitions).setTaskViewVisible(eq(mTaskView), eq(false));
+
+        mTaskView.prepareHideAnimation(new SurfaceControl.Transaction());
+
+        verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false));
+    }
+
+    @Test
+    public void testOnReleased() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        mTaskView.release();
+
+        verify(mOrganizer).removeListener(eq(mTaskView));
+        verify(mViewListener).onReleased();
+        verify(mTaskViewTransitions).removeTaskView(eq(mTaskView));
+    }
+
+    @Test
+    public void testOnTaskVanished() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskView.surfaceCreated(mock(SurfaceHolder.class));
+        mTaskView.prepareCloseAnimation();
+
+        verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId));
+    }
+
+    @Test
+    public void testOnBackPressedOnTaskRoot() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        mTaskView.onBackPressedOnTaskRoot(mTaskInfo);
+
+        verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId));
+    }
+
+    @Test
+    public void testSetOnBackPressedOnTaskRoot() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
+    }
+
+    @Test
+    public void testUnsetOnBackPressedOnTaskRoot() {
+        assumeTrue(Transitions.ENABLE_SHELL_TRANSITIONS);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        mTaskView.prepareOpenAnimation(true /* newTask */, new SurfaceControl.Transaction(),
+                new SurfaceControl.Transaction(), mTaskInfo, mLeash, wct);
+        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(true));
+
+        mTaskView.prepareCloseAnimation();
+        verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mTaskInfo.token), eq(false));
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 2b5cd60..51eec27 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -36,6 +37,7 @@
     private WindowContainerToken mToken = createMockWCToken();
     private int mParentTaskId = INVALID_TASK_ID;
     private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
+    private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
 
     public static WindowContainerToken createMockWCToken() {
         final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
@@ -60,6 +62,12 @@
         return this;
     }
 
+    public TestRunningTaskInfoBuilder setWindowingMode(
+            @WindowConfiguration.WindowingMode int windowingMode) {
+        mWindowingMode = windowingMode;
+        return this;
+    }
+
     public ActivityManager.RunningTaskInfo build() {
         final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
         info.parentTaskId = INVALID_TASK_ID;
@@ -67,6 +75,7 @@
         info.parentTaskId = mParentTaskId;
         info.configuration.windowConfiguration.setBounds(mBounds);
         info.configuration.windowConfiguration.setActivityType(mActivityType);
+        info.configuration.windowConfiguration.setWindowingMode(mWindowingMode);
         info.token = mToken;
         info.isResizeable = true;
         info.supportsMultiWindow = true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index bc701d0..185479b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -39,6 +39,7 @@
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.Log;
 import android.util.Pair;
 import android.view.WindowManager;
 
@@ -793,7 +794,7 @@
     }
 
     @Test
-    public void test_expanded_removeLastBubble_showsOverflowIfNotEmpty() {
+    public void test_expanded_removeLastBubble_collapsesIfOverflowNotEmpty() {
         // Setup
         sendUpdatedEntryAtTime(mEntryA1, 1000);
         changeExpandedStateAtTime(true, 2000);
@@ -803,7 +804,7 @@
         mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE);
         verifyUpdateReceived();
         assertThat(mBubbleData.getOverflowBubbles().size()).isGreaterThan(0);
-        assertSelectionChangedTo(mBubbleData.getOverflow());
+        assertExpandedChangedTo(false);
     }
 
     @Test
@@ -913,6 +914,31 @@
         assertSelectionChangedTo(mBubbleA2);
     }
 
+    /**
+      * - have a maxed out bubble stack & all of the bubbles have been recently accessed
+      * - bubble a notification that was posted before any of those bubbles were accessed
+      * => that bubble should be added
+     *
+      */
+    @Test
+    public void test_addOldNotifWithNewerBubbles() {
+        sendUpdatedEntryAtTime(mEntryA1, 2000);
+        sendUpdatedEntryAtTime(mEntryA2, 3000);
+        sendUpdatedEntryAtTime(mEntryA3, 4000);
+        sendUpdatedEntryAtTime(mEntryB1, 5000);
+        sendUpdatedEntryAtTime(mEntryB2, 6000);
+
+        mBubbleData.setListener(mListener);
+        sendUpdatedEntryAtTime(mEntryB3, 1000 /* postTime */, 7000 /* currentTime */);
+        verifyUpdateReceived();
+
+        // B3 is in the stack
+        assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleB3.getKey())).isNotNull();
+        // A1 is the oldest so it's in the overflow
+        assertThat(mBubbleData.getOverflowBubbleWithKey(mEntryA1.getKey())).isNotNull();
+        assertOrderChangedTo(mBubbleB3, mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2);
+    }
+
     private void verifyUpdateReceived() {
         verify(mListener).applyUpdate(mUpdateCaptor.capture());
         reset(mListener);
@@ -1014,6 +1040,12 @@
     }
 
     private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime) {
+        setCurrentTime(postTime);
+        sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
+    }
+
+    private void sendUpdatedEntryAtTime(BubbleEntry entry, long postTime, long currentTime) {
+        setCurrentTime(currentTime);
         sendUpdatedEntryAtTime(entry, postTime, true /* isTextChanged */);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index 2b9bdce..335222e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -59,19 +59,21 @@
     private int mStackOffset;
     private PointF mExpansionPoint;
     private BubblePositioner mPositioner;
-    private BubbleStackView.StackViewState mStackViewState;
+    private BubbleStackView.StackViewState mStackViewState = new BubbleStackView.StackViewState();
 
     @SuppressLint("VisibleForTests")
     @Before
     public void setUp() throws Exception {
         super.setUp();
 
-        BubbleStackView stackView = mock(BubbleStackView.class);
-        when(stackView.getState()).thenReturn(getStackViewState());
         mPositioner = new BubblePositioner(getContext(), mock(WindowManager.class));
         mPositioner.updateInternal(Configuration.ORIENTATION_PORTRAIT,
                 Insets.of(0, 0, 0, 0),
                 new Rect(0, 0, mDisplayWidth, mDisplayHeight));
+
+        BubbleStackView stackView = mock(BubbleStackView.class);
+        when(stackView.getState()).thenReturn(getStackViewState());
+
         mExpandedController = new ExpandedAnimationController(mPositioner,
                 mOnBubbleAnimatedOutAction,
                 stackView);
@@ -135,6 +137,12 @@
         testBubblesInCorrectExpandedPositions();
     }
 
+    @Test
+    public void testDragBubbleOutDoesntNPE() throws InterruptedException {
+        mExpandedController.onGestureFinished();
+        mExpandedController.dragBubbleOut(mViews.get(0), 1, 1);
+    }
+
     /** Expand the stack and wait for animations to finish. */
     private void expand() throws InterruptedException {
         mExpandedController.expandFromStack(mock(Runnable.class));
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 453050f..83d5f04 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
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
@@ -101,14 +102,21 @@
 
     @Test
     public void testSetDividePosition() {
-        mSplitLayout.setDividePosition(anyInt());
+        mSplitLayout.setDividePosition(100, false /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividePosition()).isEqualTo(100);
+        verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class));
+
+        mSplitLayout.setDividePosition(200, true /* applyLayoutChange */);
+        assertThat(mSplitLayout.getDividePosition()).isEqualTo(200);
         verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
     }
 
     @Test
     public void testSetDivideRatio() {
+        mSplitLayout.setDividePosition(200, false /* applyLayoutChange */);
         mSplitLayout.setDivideRatio(0.5f);
-        verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class));
+        assertThat(mSplitLayout.getDividePosition()).isEqualTo(
+                mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
similarity index 83%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 877b192..f622edb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.sizecompatui;
+package com.android.wm.shell.compatui;
 
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 
@@ -56,18 +56,18 @@
 import org.mockito.MockitoAnnotations;
 
 /**
- * Tests for {@link SizeCompatUIController}.
+ * Tests for {@link CompatUIController}.
  *
  * Build/Install/Run:
- *  atest WMShellUnitTests:SizeCompatUIControllerTest
+ *  atest WMShellUnitTests:CompatUIControllerTest
  */
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
-public class SizeCompatUIControllerTest extends ShellTestCase {
+public class CompatUIControllerTest extends ShellTestCase {
     private static final int DISPLAY_ID = 0;
     private static final int TASK_ID = 12;
 
-    private SizeCompatUIController mController;
+    private CompatUIController mController;
     private @Mock DisplayController mMockDisplayController;
     private @Mock DisplayInsetsController mMockDisplayInsetsController;
     private @Mock DisplayLayout mMockDisplayLayout;
@@ -75,7 +75,7 @@
     private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener;
     private @Mock SyncTransactionQueue mMockSyncQueue;
     private @Mock ShellExecutor mMockExecutor;
-    private @Mock SizeCompatUILayout mMockLayout;
+    private @Mock CompatUIWindowManager mMockLayout;
 
     @Captor
     ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor;
@@ -87,10 +87,10 @@
         doReturn(mMockDisplayLayout).when(mMockDisplayController).getDisplayLayout(anyInt());
         doReturn(DISPLAY_ID).when(mMockLayout).getDisplayId();
         doReturn(TASK_ID).when(mMockLayout).getTaskId();
-        mController = new SizeCompatUIController(mContext, mMockDisplayController,
+        mController = new CompatUIController(mContext, mMockDisplayController,
                 mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor) {
             @Override
-            SizeCompatUILayout createLayout(Context context, int displayId, int taskId,
+            CompatUIWindowManager createLayout(Context context, int displayId, int taskId,
                     Configuration taskConfig, ShellTaskOrganizer.TaskListener taskListener) {
                 return mMockLayout;
             }
@@ -105,24 +105,24 @@
     }
 
     @Test
-    public void testOnSizeCompatInfoChanged() {
+    public void testOnCompatInfoChanged() {
         final Configuration taskConfig = new Configuration();
 
         // Verify that the restart button is added with non-null size compat info.
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
         verify(mController).createLayout(any(), eq(DISPLAY_ID), eq(TASK_ID), eq(taskConfig),
                 eq(mMockTaskListener));
 
         // Verify that the restart button is updated with non-null new size compat info.
         final Configuration newTaskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, newTaskConfig, mMockTaskListener);
 
-        verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
                 true /* show */);
 
         // Verify that the restart button is removed with null size compat info.
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, null, mMockTaskListener);
 
         verify(mMockLayout).release();
     }
@@ -140,7 +140,7 @@
     public void testOnDisplayRemoved() {
         mController.onDisplayAdded(DISPLAY_ID);
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockTaskListener);
 
         mController.onDisplayRemoved(DISPLAY_ID + 1);
@@ -158,7 +158,7 @@
     @Test
     public void testOnDisplayConfigurationChanged() {
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockTaskListener);
 
         final Configuration newTaskConfig = new Configuration();
@@ -175,7 +175,7 @@
     public void testInsetsChanged() {
         mController.onDisplayAdded(DISPLAY_ID);
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig,
                 mMockTaskListener);
         InsetsState insetsState = new InsetsState();
         InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
@@ -197,7 +197,7 @@
     @Test
     public void testChangeButtonVisibilityOnImeShowHide() {
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
         // Verify that the restart button is hidden after IME is showing.
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
@@ -205,9 +205,9 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while IME is showing.
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
-        verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
                 false /* show */);
 
         // Verify button is shown after IME is hidden.
@@ -219,7 +219,7 @@
     @Test
     public void testChangeButtonVisibilityOnKeyguardOccludedChanged() {
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
         // Verify that the restart button is hidden after keyguard becomes occluded.
         mController.onKeyguardOccludedChanged(true);
@@ -227,9 +227,9 @@
         verify(mMockLayout).updateVisibility(false);
 
         // Verify button remains hidden while keyguard is occluded.
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
-        verify(mMockLayout).updateSizeCompatInfo(taskConfig, mMockTaskListener,
+        verify(mMockLayout).updateCompatInfo(taskConfig, mMockTaskListener,
                 false /* show */);
 
         // Verify button is shown after keyguard becomes not occluded.
@@ -241,7 +241,7 @@
     @Test
     public void testButtonRemainsHiddenOnKeyguardOccludedFalseWhenImeIsShowing() {
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
         mController.onKeyguardOccludedChanged(true);
@@ -264,7 +264,7 @@
     @Test
     public void testButtonRemainsHiddenOnImeHideWhenKeyguardIsOccluded() {
         final Configuration taskConfig = new Configuration();
-        mController.onSizeCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
+        mController.onCompatInfoChanged(DISPLAY_ID, TASK_ID, taskConfig, mMockTaskListener);
 
         mController.onImeVisibilityChanged(DISPLAY_ID, true /* isShowing */);
         mController.onKeyguardOccludedChanged(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
new file mode 100644
index 0000000..2c3987b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.testing.AndroidTestingRunner;
+import android.view.LayoutInflater;
+import android.view.SurfaceControlViewHost;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link CompatUILayout}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:CompatUILayoutTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class CompatUILayoutTest extends ShellTestCase {
+
+    private static final int TASK_ID = 1;
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private CompatUIController.CompatUICallback mCallback;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private SurfaceControlViewHost mViewHost;
+
+    private CompatUIWindowManager mWindowManager;
+    private CompatUILayout mCompatUILayout;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
+                mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
+                false /* hasShownHint */);
+
+        mCompatUILayout = (CompatUILayout)
+                LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, null);
+        mCompatUILayout.inject(mWindowManager);
+
+        spyOn(mWindowManager);
+        spyOn(mCompatUILayout);
+        doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+    }
+
+    @Test
+    public void testOnClickForRestartButton() {
+        final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
+        button.performClick();
+
+        verify(mWindowManager).onRestartButtonClicked();
+        doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
+        verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+    }
+
+    @Test
+    public void testOnLongClickForRestartButton() {
+        doNothing().when(mWindowManager).onRestartButtonLongClicked();
+
+        final ImageButton button = mCompatUILayout.findViewById(R.id.size_compat_restart_button);
+        button.performLongClick();
+
+        verify(mWindowManager).onRestartButtonLongClicked();
+    }
+
+    @Test
+    public void testOnClickForSizeCompatHint() {
+        mWindowManager.createLayout(true /* show */);
+        final LinearLayout sizeCompatHint = mCompatUILayout.findViewById(R.id.size_compat_hint);
+        sizeCompatHint.performClick();
+
+        verify(mCompatUILayout).setSizeCompatHintVisibility(/* show= */ false);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
new file mode 100644
index 0000000..d5dcf2e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -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.wm.shell.compatui;
+
+import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.view.DisplayInfo;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link CompatUIWindowManager}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:CompatUIWindowManagerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class CompatUIWindowManagerTest extends ShellTestCase {
+
+    private static final int TASK_ID = 1;
+
+    @Mock private SyncTransactionQueue mSyncTransactionQueue;
+    @Mock private CompatUIController.CompatUICallback mCallback;
+    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
+    @Mock private CompatUILayout mCompatUILayout;
+    @Mock private SurfaceControlViewHost mViewHost;
+    private Configuration mTaskConfig;
+
+    private CompatUIWindowManager mWindowManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTaskConfig = new Configuration();
+
+        mWindowManager = new CompatUIWindowManager(mContext, new Configuration(),
+                mSyncTransactionQueue, mCallback, TASK_ID, mTaskListener, new DisplayLayout(),
+                false /* hasShownHint */);
+
+        spyOn(mWindowManager);
+        doReturn(mCompatUILayout).when(mWindowManager).inflateCompatUILayout();
+        doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost();
+    }
+
+    @Test
+    public void testCreateSizeCompatButton() {
+        // Not create layout if show is false.
+        mWindowManager.createLayout(false /* show */);
+
+        verify(mWindowManager, never()).inflateCompatUILayout();
+
+        // Not create hint popup.
+        mWindowManager.mShouldShowHint = false;
+        mWindowManager.createLayout(true /* show */);
+
+        verify(mWindowManager).inflateCompatUILayout();
+        verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+
+        // Create hint popup.
+        mWindowManager.release();
+        mWindowManager.mShouldShowHint = true;
+        mWindowManager.createLayout(true /* show */);
+
+        verify(mWindowManager, times(2)).inflateCompatUILayout();
+        assertNotNull(mCompatUILayout);
+        verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+        assertFalse(mWindowManager.mShouldShowHint);
+    }
+
+    @Test
+    public void testRelease() {
+        mWindowManager.createLayout(true /* show */);
+
+        verify(mWindowManager).inflateCompatUILayout();
+
+        mWindowManager.release();
+
+        verify(mViewHost).release();
+    }
+
+    @Test
+    public void testUpdateCompatInfo() {
+        mWindowManager.createLayout(true /* show */);
+
+        // No diff
+        clearInvocations(mWindowManager);
+        mWindowManager.updateCompatInfo(mTaskConfig, mTaskListener, true /* show */);
+
+        verify(mWindowManager, never()).updateSurfacePosition();
+        verify(mWindowManager, never()).release();
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+
+        // Change task listener, recreate button.
+        clearInvocations(mWindowManager);
+        final ShellTaskOrganizer.TaskListener newTaskListener = mock(
+                ShellTaskOrganizer.TaskListener.class);
+        mWindowManager.updateCompatInfo(mTaskConfig, newTaskListener,
+                true /* show */);
+
+        verify(mWindowManager).release();
+        verify(mWindowManager).createLayout(anyBoolean());
+
+        // Change task bounds, update position.
+        clearInvocations(mWindowManager);
+        final Configuration newTaskConfiguration = new Configuration();
+        newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
+        mWindowManager.updateCompatInfo(newTaskConfiguration, newTaskListener,
+                true /* show */);
+
+        verify(mWindowManager).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateDisplayLayout() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+
+        mWindowManager.updateDisplayLayout(displayLayout1);
+        verify(mWindowManager).updateSurfacePosition();
+
+        // No update if the display bounds is the same.
+        clearInvocations(mWindowManager);
+        final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
+        mWindowManager.updateDisplayLayout(displayLayout2);
+        verify(mWindowManager, never()).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateDisplayLayoutInsets() {
+        final DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = 1000;
+        displayInfo.logicalHeight = 2000;
+        final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
+                mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
+
+        mWindowManager.updateDisplayLayout(displayLayout);
+        verify(mWindowManager).updateSurfacePosition();
+
+        // Update if the insets change on the existing display layout
+        clearInvocations(mWindowManager);
+        InsetsState insetsState = new InsetsState();
+        InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+        insetsSource.setFrame(0, 0, 1000, 1000);
+        insetsState.addSource(insetsSource);
+        displayLayout.setInsets(mContext.getResources(), insetsState);
+        mWindowManager.updateDisplayLayout(displayLayout);
+        verify(mWindowManager).updateSurfacePosition();
+    }
+
+    @Test
+    public void testUpdateVisibility() {
+        // Create button if it is not created.
+        mWindowManager.mCompatUILayout = null;
+        mWindowManager.updateVisibility(true /* show */);
+
+        verify(mWindowManager).createLayout(true /* show */);
+
+        // Hide button.
+        clearInvocations(mWindowManager);
+        doReturn(View.VISIBLE).when(mCompatUILayout).getVisibility();
+        mWindowManager.updateVisibility(false /* show */);
+
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mCompatUILayout).setVisibility(View.GONE);
+
+        // Show button.
+        doReturn(View.GONE).when(mCompatUILayout).getVisibility();
+        mWindowManager.updateVisibility(true /* show */);
+
+        verify(mWindowManager, never()).createLayout(anyBoolean());
+        verify(mCompatUILayout).setVisibility(View.VISIBLE);
+    }
+
+    @Test
+    public void testAttachToParentSurface() {
+        final SurfaceControl.Builder b = new SurfaceControl.Builder();
+        mWindowManager.attachToParentSurface(b);
+
+        verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
+    }
+
+    @Test
+    public void testOnRestartButtonClicked() {
+        mWindowManager.onRestartButtonClicked();
+
+        verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
+    }
+
+    @Test
+    public void testOnRestartButtonLongClicked_showHint() {
+       // Not create hint popup.
+        mWindowManager.mShouldShowHint = false;
+        mWindowManager.createLayout(true /* show */);
+
+        verify(mWindowManager).inflateCompatUILayout();
+        verify(mCompatUILayout).setSizeCompatHintVisibility(false /* show */);
+
+        mWindowManager.onRestartButtonLongClicked();
+
+        verify(mCompatUILayout).setSizeCompatHintVisibility(true /* show */);
+    }
+
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index a6215d3..8e30f65 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -188,7 +188,7 @@
         final Rect newBounds = new Rect(50, 50, 100, 75);
         mPipBoundsState.setBounds(currentBounds);
 
-        mPipBoundsState.setPipExclusionBoundsChangeCallback(callback);
+        mPipBoundsState.addPipExclusionBoundsChangeCallback(callback);
         // Setting the listener immediately calls back with the current bounds.
         verify(callback).accept(currentBounds);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
deleted file mode 100644
index 3a14a33..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatHintPopupTest.java
+++ /dev/null
@@ -1,85 +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.wm.shell.sizecompatui;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.testing.AndroidTestingRunner;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link SizeCompatHintPopup}.
- *
- * Build/Install/Run:
- *  atest WMShellUnitTests:SizeCompatHintPopupTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatHintPopupTest extends ShellTestCase {
-
-    @Mock private SyncTransactionQueue mSyncTransactionQueue;
-    @Mock private SizeCompatUIController.SizeCompatUICallback mCallback;
-    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
-    @Mock private DisplayLayout mDisplayLayout;
-
-    private SizeCompatUILayout mLayout;
-    private SizeCompatHintPopup mHint;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        final int taskId = 1;
-        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext,
-                new Configuration(), taskId, mTaskListener, mDisplayLayout,
-                false /* hasShownHint */);
-        mHint = (SizeCompatHintPopup)
-                LayoutInflater.from(mContext).inflate(R.layout.size_compat_mode_hint, null);
-        mHint.inject(mLayout);
-
-        spyOn(mLayout);
-    }
-
-    @Test
-    public void testOnClick() {
-        doNothing().when(mLayout).dismissHint();
-
-        final LinearLayout hintPopup = mHint.findViewById(R.id.size_compat_hint_popup);
-        hintPopup.performClick();
-
-        verify(mLayout).dismissHint();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
deleted file mode 100644
index a20a5e9..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatRestartButtonTest.java
+++ /dev/null
@@ -1,95 +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.wm.shell.sizecompatui;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.testing.AndroidTestingRunner;
-import android.view.LayoutInflater;
-import android.widget.ImageButton;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link SizeCompatRestartButton}.
- *
- * Build/Install/Run:
- *  atest WMShellUnitTests:SizeCompatRestartButtonTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatRestartButtonTest extends ShellTestCase {
-
-    private static final int TASK_ID = 1;
-
-    @Mock private SyncTransactionQueue mSyncTransactionQueue;
-    @Mock private SizeCompatUIController.SizeCompatUICallback mCallback;
-    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
-    @Mock private DisplayLayout mDisplayLayout;
-
-    private SizeCompatUILayout mLayout;
-    private SizeCompatRestartButton mButton;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext,
-                new Configuration(), TASK_ID, mTaskListener, mDisplayLayout,
-                false /* hasShownHint */);
-        mButton = (SizeCompatRestartButton)
-                LayoutInflater.from(mContext).inflate(R.layout.size_compat_ui, null);
-        mButton.inject(mLayout);
-
-        spyOn(mLayout);
-    }
-
-    @Test
-    public void testOnClick() {
-        final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button);
-        button.performClick();
-
-        verify(mLayout).onRestartButtonClicked();
-        verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
-    }
-
-    @Test
-    public void testOnLongClick() {
-        doNothing().when(mLayout).onRestartButtonLongClicked();
-
-        final ImageButton button = mButton.findViewById(R.id.size_compat_restart_button);
-        button.performLongClick();
-
-        verify(mLayout).onRestartButtonLongClicked();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
deleted file mode 100644
index eb9305b..0000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sizecompatui/SizeCompatUILayoutTest.java
+++ /dev/null
@@ -1,284 +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.wm.shell.sizecompatui;
-
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.testing.AndroidTestingRunner;
-import android.view.DisplayInfo;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Tests for {@link SizeCompatUILayout}.
- *
- * Build/Install/Run:
- *  atest WMShellUnitTests:SizeCompatUILayoutTest
- */
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class SizeCompatUILayoutTest extends ShellTestCase {
-
-    private static final int TASK_ID = 1;
-
-    @Mock private SyncTransactionQueue mSyncTransactionQueue;
-    @Mock private SizeCompatUIController.SizeCompatUICallback mCallback;
-    @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
-    @Mock private DisplayLayout mDisplayLayout;
-    @Mock private SizeCompatRestartButton mButton;
-    @Mock private SizeCompatHintPopup mHint;
-    private Configuration mTaskConfig;
-
-    private SizeCompatUILayout mLayout;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mTaskConfig = new Configuration();
-
-        mLayout = new SizeCompatUILayout(mSyncTransactionQueue, mCallback, mContext,
-                new Configuration(), TASK_ID, mTaskListener, new DisplayLayout(),
-                false /* hasShownHint */);
-
-        spyOn(mLayout);
-        spyOn(mLayout.mButtonWindowManager);
-        doReturn(mButton).when(mLayout.mButtonWindowManager).createSizeCompatButton();
-
-        final SizeCompatUIWindowManager hintWindowManager = mLayout.createHintWindowManager();
-        spyOn(hintWindowManager);
-        doReturn(mHint).when(hintWindowManager).createSizeCompatHint();
-        doReturn(hintWindowManager).when(mLayout).createHintWindowManager();
-    }
-
-    @Test
-    public void testCreateSizeCompatButton() {
-        // Not create button if show is false.
-        mLayout.createSizeCompatButton(false /* show */);
-
-        verify(mLayout.mButtonWindowManager, never()).createSizeCompatButton();
-        assertNull(mLayout.mButton);
-        assertNull(mLayout.mHintWindowManager);
-        assertNull(mLayout.mHint);
-
-        // Not create hint popup.
-        mLayout.mShouldShowHint = false;
-        mLayout.createSizeCompatButton(true /* show */);
-
-        verify(mLayout.mButtonWindowManager).createSizeCompatButton();
-        assertNotNull(mLayout.mButton);
-        assertNull(mLayout.mHintWindowManager);
-        assertNull(mLayout.mHint);
-
-        // Create hint popup.
-        mLayout.release();
-        mLayout.mShouldShowHint = true;
-        mLayout.createSizeCompatButton(true /* show */);
-
-        verify(mLayout.mButtonWindowManager, times(2)).createSizeCompatButton();
-        assertNotNull(mLayout.mButton);
-        assertNotNull(mLayout.mHintWindowManager);
-        verify(mLayout.mHintWindowManager).createSizeCompatHint();
-        assertNotNull(mLayout.mHint);
-        assertFalse(mLayout.mShouldShowHint);
-    }
-
-    @Test
-    public void testRelease() {
-        mLayout.createSizeCompatButton(true /* show */);
-        final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
-
-        mLayout.release();
-
-        assertNull(mLayout.mButton);
-        assertNull(mLayout.mHint);
-        verify(hintWindowManager).release();
-        assertNull(mLayout.mHintWindowManager);
-        verify(mLayout.mButtonWindowManager).release();
-    }
-
-    @Test
-    public void testUpdateSizeCompatInfo() {
-        mLayout.createSizeCompatButton(true /* show */);
-
-        // No diff
-        clearInvocations(mLayout);
-        mLayout.updateSizeCompatInfo(mTaskConfig, mTaskListener, true /* show */);
-
-        verify(mLayout, never()).updateButtonSurfacePosition();
-        verify(mLayout, never()).release();
-        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
-
-        // Change task listener, recreate button.
-        clearInvocations(mLayout);
-        final ShellTaskOrganizer.TaskListener newTaskListener = mock(
-                ShellTaskOrganizer.TaskListener.class);
-        mLayout.updateSizeCompatInfo(mTaskConfig, newTaskListener,
-                true /* show */);
-
-        verify(mLayout).release();
-        verify(mLayout).createSizeCompatButton(anyBoolean());
-
-        // Change task bounds, update position.
-        clearInvocations(mLayout);
-        final Configuration newTaskConfiguration = new Configuration();
-        newTaskConfiguration.windowConfiguration.setBounds(new Rect(0, 1000, 0, 2000));
-        mLayout.updateSizeCompatInfo(newTaskConfiguration, newTaskListener,
-                true /* show */);
-
-        verify(mLayout).updateButtonSurfacePosition();
-        verify(mLayout).updateHintSurfacePosition();
-    }
-
-    @Test
-    public void testUpdateDisplayLayout() {
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = 1000;
-        displayInfo.logicalHeight = 2000;
-        final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo,
-                mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
-
-        mLayout.updateDisplayLayout(displayLayout1);
-        verify(mLayout).updateButtonSurfacePosition();
-        verify(mLayout).updateHintSurfacePosition();
-
-        // No update if the display bounds is the same.
-        clearInvocations(mLayout);
-        final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo,
-                mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false);
-        mLayout.updateDisplayLayout(displayLayout2);
-        verify(mLayout, never()).updateButtonSurfacePosition();
-        verify(mLayout, never()).updateHintSurfacePosition();
-    }
-
-    @Test
-    public void testUpdateDisplayLayoutInsets() {
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = 1000;
-        displayInfo.logicalHeight = 2000;
-        final DisplayLayout displayLayout = new DisplayLayout(displayInfo,
-                mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false);
-
-        mLayout.updateDisplayLayout(displayLayout);
-        verify(mLayout).updateButtonSurfacePosition();
-        verify(mLayout).updateHintSurfacePosition();
-
-        // Update if the insets change on the existing display layout
-        clearInvocations(mLayout);
-        InsetsState insetsState = new InsetsState();
-        InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
-        insetsSource.setFrame(0, 0, 1000, 1000);
-        insetsState.addSource(insetsSource);
-        displayLayout.setInsets(mContext.getResources(), insetsState);
-        mLayout.updateDisplayLayout(displayLayout);
-        verify(mLayout).updateButtonSurfacePosition();
-        verify(mLayout).updateHintSurfacePosition();
-    }
-
-    @Test
-    public void testUpdateVisibility() {
-        // Create button if it is not created.
-        mLayout.mButton = null;
-        mLayout.updateVisibility(true /* show */);
-
-        verify(mLayout).createSizeCompatButton(true /* show */);
-
-        // Hide button.
-        clearInvocations(mLayout);
-        doReturn(View.VISIBLE).when(mButton).getVisibility();
-        mLayout.updateVisibility(false /* show */);
-
-        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
-        verify(mButton).setVisibility(View.GONE);
-
-        // Show button.
-        doReturn(View.GONE).when(mButton).getVisibility();
-        mLayout.updateVisibility(true /* show */);
-
-        verify(mLayout, never()).createSizeCompatButton(anyBoolean());
-        verify(mButton).setVisibility(View.VISIBLE);
-    }
-
-    @Test
-    public void testAttachToParentSurface() {
-        final SurfaceControl.Builder b = new SurfaceControl.Builder();
-        mLayout.attachToParentSurface(b);
-
-        verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b);
-    }
-
-    @Test
-    public void testOnRestartButtonClicked() {
-        mLayout.onRestartButtonClicked();
-
-        verify(mCallback).onSizeCompatRestartButtonClicked(TASK_ID);
-    }
-
-    @Test
-    public void testOnRestartButtonLongClicked_showHint() {
-        mLayout.dismissHint();
-
-        assertNull(mLayout.mHint);
-
-        mLayout.onRestartButtonLongClicked();
-
-        assertNotNull(mLayout.mHint);
-    }
-
-    @Test
-    public void testDismissHint() {
-        mLayout.onRestartButtonLongClicked();
-        final SizeCompatUIWindowManager hintWindowManager = mLayout.mHintWindowManager;
-        assertNotNull(mLayout.mHint);
-        assertNotNull(hintWindowManager);
-
-        mLayout.dismissHint();
-
-        assertNull(mLayout.mHint);
-        assertNull(mLayout.mHintWindowManager);
-        verify(hintWindowManager).release();
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 1eae625..be1ef09 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.splitscreen;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -24,7 +25,11 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
+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_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitTestUtils.createMockSurface;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
 
@@ -66,7 +71,6 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
@@ -133,6 +137,40 @@
     }
 
     @Test
+    public void testLaunchToSide() {
+        ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder()
+                .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
+        ActivityManager.RunningTaskInfo reparentTask = new TestRunningTaskInfoBuilder()
+                .setParentTaskId(mMainStage.mRootTaskInfo.taskId).build();
+
+        // Create a request to start a new task in side stage
+        TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, newTask, null);
+        IBinder transition = mock(IBinder.class);
+        WindowContainerTransaction result =
+                mStageCoordinator.handleRequest(transition, request);
+
+        // it should handle the transition to enter split screen.
+        assertNotNull(result);
+        assertTrue(containsSplitEnter(result));
+
+        // simulate the transition
+        TransitionInfo.Change openChange = createChange(TRANSIT_OPEN, newTask);
+        TransitionInfo.Change reparentChange = createChange(TRANSIT_CHANGE, reparentTask);
+
+        TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
+        info.addChange(openChange);
+        info.addChange(reparentChange);
+        mSideStage.onTaskAppeared(newTask, createMockSurface());
+        mMainStage.onTaskAppeared(reparentTask, createMockSurface());
+        boolean accepted = mStageCoordinator.startAnimation(transition, info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(Transitions.TransitionFinishCallback.class));
+        assertTrue(accepted);
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+    }
+
+    @Test
     public void testLaunchPair() {
         TransitionInfo info = createEnterPairInfo();
 
@@ -215,7 +253,9 @@
         enterSplit();
 
         ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
-                .setActivityType(ACTIVITY_TYPE_HOME).build();
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setActivityType(ACTIVITY_TYPE_HOME)
+                .build();
 
         // Create a request to bring home forward
         TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null);
@@ -246,6 +286,64 @@
     }
 
     @Test
+    public void testDismissFromBeingOccluded() {
+        enterSplit();
+
+        ActivityManager.RunningTaskInfo normalTask = new TestRunningTaskInfoBuilder()
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .build();
+
+        // Create a request to bring a normal task forward
+        TransitionRequestInfo request =
+                new TransitionRequestInfo(TRANSIT_TO_FRONT, normalTask, null);
+        IBinder transition = mock(IBinder.class);
+        WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request);
+
+        assertTrue(containsSplitExit(result));
+
+        // make sure we haven't made any local changes yet (need to wait until transition is ready)
+        assertTrue(mStageCoordinator.isSplitScreenVisible());
+
+        // simulate the transition
+        TransitionInfo.Change normalChange = createChange(TRANSIT_TO_FRONT, normalTask);
+        TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild);
+        TransitionInfo.Change sideChange = createChange(TRANSIT_TO_BACK, mSideChild);
+
+        TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
+        info.addChange(normalChange);
+        info.addChange(mainChange);
+        info.addChange(sideChange);
+        mMainStage.onTaskVanished(mMainChild);
+        mSideStage.onTaskVanished(mSideChild);
+        mStageCoordinator.startAnimation(transition, info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(Transitions.TransitionFinishCallback.class));
+        assertFalse(mStageCoordinator.isSplitScreenVisible());
+    }
+
+    @Test
+    public void testDismissFromMultiWindowSupport() {
+        enterSplit();
+
+        // simulate the transition
+        TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild);
+        TransitionInfo.Change sideChange = createChange(TRANSIT_TO_BACK, mSideChild);
+        TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
+        info.addChange(mainChange);
+        info.addChange(sideChange);
+        IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+                new WindowContainerTransaction(), mStageCoordinator,
+                EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE);
+        boolean accepted = mStageCoordinator.startAnimation(transition, info,
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                mock(Transitions.TransitionFinishCallback.class));
+        assertTrue(accepted);
+        assertFalse(mStageCoordinator.isSplitScreenVisible());
+    }
+
+    @Test
     public void testDismissSnap() {
         enterSplit();
 
@@ -256,8 +354,9 @@
         TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
         info.addChange(mainChange);
         info.addChange(sideChange);
-        IBinder transition = mStageCoordinator.onSnappedToDismissTransition(
-                false /* mainStageToTop */);
+        IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+                new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER,
+                STAGE_TYPE_SIDE);
         mMainStage.onTaskVanished(mMainChild);
         mSideStage.onTaskVanished(mSideChild);
         boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -324,6 +423,22 @@
                 true /* includingTopTask */);
     }
 
+    private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
+        boolean movedMainToFront = false;
+        boolean movedSideToFront = false;
+        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+            WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
+            if (op.getType() == HIERARCHY_OP_TYPE_REORDER) {
+                if (op.getContainer() == mMainStage.mRootTaskInfo.token.asBinder()) {
+                    movedMainToFront = true;
+                } else if (op.getContainer() == mSideStage.mRootTaskInfo.token.asBinder()) {
+                    movedSideToFront = true;
+                }
+            }
+        }
+        return movedMainToFront && movedSideToFront;
+    }
+
     private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) {
         // reparenting of child tasks to null constitutes exiting split.
         boolean reparentedMain = false;
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 85f6789..fb6300c 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
@@ -114,6 +114,7 @@
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
         when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
+        when(mSplitLayout.isLandscape()).thenReturn(false);
     }
 
     @Test
@@ -168,8 +169,9 @@
 
         mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
 
-        verify(mMainUnfoldController).onLayoutChanged(mBounds2);
-        verify(mSideUnfoldController).onLayoutChanged(mBounds1);
+        verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                false);
+        verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false);
     }
 
     @Test
@@ -180,8 +182,10 @@
 
         mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
 
-        verify(mMainUnfoldController).onLayoutChanged(mBounds1);
-        verify(mSideUnfoldController).onLayoutChanged(mBounds2);
+        verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT,
+                false);
+        verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                false);
     }
 
     @Test
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 22904a0..136fc6c 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -902,6 +902,27 @@
   return log_stream.str();
 }
 
+base::expected<uint32_t, NullOrIOError> AssetManager2::GetParentThemeResourceId(uint32_t resid)
+const {
+  auto entry = FindEntry(resid, 0u /* density_override */,
+                         false /* stop_at_first_match */,
+                         false /* ignore_configuration */);
+  if (!entry.has_value()) {
+    return base::unexpected(entry.error());
+  }
+
+  auto entry_map = std::get_if<incfs::verified_map_ptr<ResTable_map_entry>>(&entry->entry);
+  if (entry_map == nullptr) {
+    // Not a bag, nothing to do.
+    return base::unexpected(std::nullopt);
+  }
+
+  auto map = *entry_map;
+  const uint32_t parent_resid = dtohl(map->parent.ident);
+
+  return parent_resid;
+}
+
 base::expected<AssetManager2::ResourceName, NullOrIOError> AssetManager2::GetResourceName(
     uint32_t resid) const {
   auto result = FindEntry(resid, 0u /* density_override */, true /* stop_at_first_match */,
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index 2c3567a..2c005fd 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -34,206 +34,209 @@
     /* 30 */ {'H', 'a', 'n', 't'},
     /* 31 */ {'H', 'e', 'b', 'r'},
     /* 32 */ {'H', 'l', 'u', 'w'},
-    /* 33 */ {'H', 'm', 'n', 'g'},
-    /* 34 */ {'H', 'm', 'n', 'p'},
-    /* 35 */ {'I', 't', 'a', 'l'},
-    /* 36 */ {'J', 'p', 'a', 'n'},
-    /* 37 */ {'K', 'a', 'l', 'i'},
-    /* 38 */ {'K', 'a', 'n', 'a'},
-    /* 39 */ {'K', 'h', 'a', 'r'},
-    /* 40 */ {'K', 'h', 'm', 'r'},
-    /* 41 */ {'K', 'i', 't', 's'},
-    /* 42 */ {'K', 'n', 'd', 'a'},
-    /* 43 */ {'K', 'o', 'r', 'e'},
-    /* 44 */ {'L', 'a', 'n', 'a'},
-    /* 45 */ {'L', 'a', 'o', 'o'},
-    /* 46 */ {'L', 'a', 't', 'n'},
-    /* 47 */ {'L', 'e', 'p', 'c'},
-    /* 48 */ {'L', 'i', 'n', 'a'},
-    /* 49 */ {'L', 'i', 's', 'u'},
-    /* 50 */ {'L', 'y', 'c', 'i'},
-    /* 51 */ {'L', 'y', 'd', 'i'},
-    /* 52 */ {'M', 'a', 'n', 'd'},
-    /* 53 */ {'M', 'a', 'n', 'i'},
-    /* 54 */ {'M', 'e', 'd', 'f'},
-    /* 55 */ {'M', 'e', 'r', 'c'},
-    /* 56 */ {'M', 'l', 'y', 'm'},
-    /* 57 */ {'M', 'o', 'n', 'g'},
-    /* 58 */ {'M', 'r', 'o', 'o'},
-    /* 59 */ {'M', 'y', 'm', 'r'},
-    /* 60 */ {'N', 'a', 'r', 'b'},
-    /* 61 */ {'N', 'k', 'o', 'o'},
-    /* 62 */ {'N', 's', 'h', 'u'},
-    /* 63 */ {'O', 'g', 'a', 'm'},
-    /* 64 */ {'O', 'l', 'c', 'k'},
-    /* 65 */ {'O', 'r', 'k', 'h'},
-    /* 66 */ {'O', 'r', 'y', 'a'},
-    /* 67 */ {'O', 's', 'g', 'e'},
+    /* 33 */ {'H', 'm', 'n', 'p'},
+    /* 34 */ {'I', 't', 'a', 'l'},
+    /* 35 */ {'J', 'p', 'a', 'n'},
+    /* 36 */ {'K', 'a', 'l', 'i'},
+    /* 37 */ {'K', 'a', 'n', 'a'},
+    /* 38 */ {'K', 'h', 'a', 'r'},
+    /* 39 */ {'K', 'h', 'm', 'r'},
+    /* 40 */ {'K', 'i', 't', 's'},
+    /* 41 */ {'K', 'n', 'd', 'a'},
+    /* 42 */ {'K', 'o', 'r', 'e'},
+    /* 43 */ {'L', 'a', 'n', 'a'},
+    /* 44 */ {'L', 'a', 'o', 'o'},
+    /* 45 */ {'L', 'a', 't', 'n'},
+    /* 46 */ {'L', 'e', 'p', 'c'},
+    /* 47 */ {'L', 'i', 'n', 'a'},
+    /* 48 */ {'L', 'i', 's', 'u'},
+    /* 49 */ {'L', 'y', 'c', 'i'},
+    /* 50 */ {'L', 'y', 'd', 'i'},
+    /* 51 */ {'M', 'a', 'n', 'd'},
+    /* 52 */ {'M', 'a', 'n', 'i'},
+    /* 53 */ {'M', 'e', 'd', 'f'},
+    /* 54 */ {'M', 'e', 'r', 'c'},
+    /* 55 */ {'M', 'l', 'y', 'm'},
+    /* 56 */ {'M', 'o', 'n', 'g'},
+    /* 57 */ {'M', 'r', 'o', 'o'},
+    /* 58 */ {'M', 'y', 'm', 'r'},
+    /* 59 */ {'N', 'a', 'r', 'b'},
+    /* 60 */ {'N', 'k', 'o', 'o'},
+    /* 61 */ {'N', 's', 'h', 'u'},
+    /* 62 */ {'O', 'g', 'a', 'm'},
+    /* 63 */ {'O', 'l', 'c', 'k'},
+    /* 64 */ {'O', 'r', 'k', 'h'},
+    /* 65 */ {'O', 'r', 'y', 'a'},
+    /* 66 */ {'O', 's', 'g', 'e'},
+    /* 67 */ {'O', 'u', 'g', 'r'},
     /* 68 */ {'P', 'a', 'u', 'c'},
     /* 69 */ {'P', 'h', 'l', 'i'},
     /* 70 */ {'P', 'h', 'n', 'x'},
     /* 71 */ {'P', 'l', 'r', 'd'},
     /* 72 */ {'P', 'r', 't', 'i'},
-    /* 73 */ {'R', 'u', 'n', 'r'},
-    /* 74 */ {'S', 'a', 'm', 'r'},
-    /* 75 */ {'S', 'a', 'r', 'b'},
-    /* 76 */ {'S', 'a', 'u', 'r'},
-    /* 77 */ {'S', 'g', 'n', 'w'},
-    /* 78 */ {'S', 'i', 'n', 'h'},
-    /* 79 */ {'S', 'o', 'g', 'd'},
-    /* 80 */ {'S', 'o', 'r', 'a'},
-    /* 81 */ {'S', 'o', 'y', 'o'},
-    /* 82 */ {'S', 'y', 'r', 'c'},
-    /* 83 */ {'T', 'a', 'l', 'e'},
-    /* 84 */ {'T', 'a', 'l', 'u'},
-    /* 85 */ {'T', 'a', 'm', 'l'},
-    /* 86 */ {'T', 'a', 'n', 'g'},
-    /* 87 */ {'T', 'a', 'v', 't'},
-    /* 88 */ {'T', 'e', 'l', 'u'},
-    /* 89 */ {'T', 'f', 'n', 'g'},
-    /* 90 */ {'T', 'h', 'a', 'a'},
-    /* 91 */ {'T', 'h', 'a', 'i'},
-    /* 92 */ {'T', 'i', 'b', 't'},
-    /* 93 */ {'U', 'g', 'a', 'r'},
-    /* 94 */ {'V', 'a', 'i', 'i'},
-    /* 95 */ {'W', 'c', 'h', 'o'},
-    /* 96 */ {'X', 'p', 'e', 'o'},
-    /* 97 */ {'X', 's', 'u', 'x'},
-    /* 98 */ {'Y', 'i', 'i', 'i'},
-    /* 99 */ {'~', '~', '~', 'A'},
-    /* 100 */ {'~', '~', '~', 'B'},
+    /* 73 */ {'R', 'o', 'h', 'g'},
+    /* 74 */ {'R', 'u', 'n', 'r'},
+    /* 75 */ {'S', 'a', 'm', 'r'},
+    /* 76 */ {'S', 'a', 'r', 'b'},
+    /* 77 */ {'S', 'a', 'u', 'r'},
+    /* 78 */ {'S', 'g', 'n', 'w'},
+    /* 79 */ {'S', 'i', 'n', 'h'},
+    /* 80 */ {'S', 'o', 'g', 'd'},
+    /* 81 */ {'S', 'o', 'r', 'a'},
+    /* 82 */ {'S', 'o', 'y', 'o'},
+    /* 83 */ {'S', 'y', 'r', 'c'},
+    /* 84 */ {'T', 'a', 'l', 'e'},
+    /* 85 */ {'T', 'a', 'l', 'u'},
+    /* 86 */ {'T', 'a', 'm', 'l'},
+    /* 87 */ {'T', 'a', 'n', 'g'},
+    /* 88 */ {'T', 'a', 'v', 't'},
+    /* 89 */ {'T', 'e', 'l', 'u'},
+    /* 90 */ {'T', 'f', 'n', 'g'},
+    /* 91 */ {'T', 'h', 'a', 'a'},
+    /* 92 */ {'T', 'h', 'a', 'i'},
+    /* 93 */ {'T', 'i', 'b', 't'},
+    /* 94 */ {'T', 'n', 's', 'a'},
+    /* 95 */ {'T', 'o', 't', 'o'},
+    /* 96 */ {'U', 'g', 'a', 'r'},
+    /* 97 */ {'V', 'a', 'i', 'i'},
+    /* 98 */ {'W', 'c', 'h', 'o'},
+    /* 99 */ {'X', 'p', 'e', 'o'},
+    /* 100 */ {'X', 's', 'u', 'x'},
+    /* 101 */ {'Y', 'i', 'i', 'i'},
+    /* 102 */ {'~', '~', '~', 'A'},
+    /* 103 */ {'~', '~', '~', 'B'},
 };
 
 
 const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
-    {0x61610000u, 46u}, // aa -> Latn
-    {0xA0000000u, 46u}, // aai -> Latn
-    {0xA8000000u, 46u}, // aak -> Latn
-    {0xD0000000u, 46u}, // aau -> Latn
+    {0x61610000u, 45u}, // aa -> Latn
+    {0xA0000000u, 45u}, // aai -> Latn
+    {0xA8000000u, 45u}, // aak -> Latn
+    {0xD0000000u, 45u}, // aau -> Latn
     {0x61620000u, 18u}, // ab -> Cyrl
-    {0xA0200000u, 46u}, // abi -> Latn
+    {0xA0200000u, 45u}, // abi -> Latn
     {0xC0200000u, 18u}, // abq -> Cyrl
-    {0xC4200000u, 46u}, // abr -> Latn
-    {0xCC200000u, 46u}, // abt -> Latn
-    {0xE0200000u, 46u}, // aby -> Latn
-    {0x8C400000u, 46u}, // acd -> Latn
-    {0x90400000u, 46u}, // ace -> Latn
-    {0x9C400000u, 46u}, // ach -> Latn
-    {0x80600000u, 46u}, // ada -> Latn
-    {0x90600000u, 46u}, // ade -> Latn
-    {0xA4600000u, 46u}, // adj -> Latn
-    {0xBC600000u, 92u}, // adp -> Tibt
+    {0xC4200000u, 45u}, // abr -> Latn
+    {0xCC200000u, 45u}, // abt -> Latn
+    {0xE0200000u, 45u}, // aby -> Latn
+    {0x8C400000u, 45u}, // acd -> Latn
+    {0x90400000u, 45u}, // ace -> Latn
+    {0x9C400000u, 45u}, // ach -> Latn
+    {0x80600000u, 45u}, // ada -> Latn
+    {0x90600000u, 45u}, // ade -> Latn
+    {0xA4600000u, 45u}, // adj -> Latn
+    {0xBC600000u, 93u}, // adp -> Tibt
     {0xE0600000u, 18u}, // ady -> Cyrl
-    {0xE4600000u, 46u}, // adz -> Latn
+    {0xE4600000u, 45u}, // adz -> Latn
     {0x61650000u,  5u}, // ae -> Avst
     {0x84800000u,  2u}, // aeb -> Arab
-    {0xE0800000u, 46u}, // aey -> Latn
-    {0x61660000u, 46u}, // af -> Latn
-    {0x88C00000u, 46u}, // agc -> Latn
-    {0x8CC00000u, 46u}, // agd -> Latn
-    {0x98C00000u, 46u}, // agg -> Latn
-    {0xB0C00000u, 46u}, // agm -> Latn
-    {0xB8C00000u, 46u}, // ago -> Latn
-    {0xC0C00000u, 46u}, // agq -> Latn
-    {0x80E00000u, 46u}, // aha -> Latn
-    {0xACE00000u, 46u}, // ahl -> Latn
+    {0xE0800000u, 45u}, // aey -> Latn
+    {0x61660000u, 45u}, // af -> Latn
+    {0x88C00000u, 45u}, // agc -> Latn
+    {0x8CC00000u, 45u}, // agd -> Latn
+    {0x98C00000u, 45u}, // agg -> Latn
+    {0xB0C00000u, 45u}, // agm -> Latn
+    {0xB8C00000u, 45u}, // ago -> Latn
+    {0xC0C00000u, 45u}, // agq -> Latn
+    {0x80E00000u, 45u}, // aha -> Latn
+    {0xACE00000u, 45u}, // ahl -> Latn
     {0xB8E00000u,  1u}, // aho -> Ahom
-    {0x99200000u, 46u}, // ajg -> Latn
-    {0x616B0000u, 46u}, // ak -> Latn
-    {0xA9400000u, 97u}, // akk -> Xsux
-    {0x81600000u, 46u}, // ala -> Latn
-    {0xA1600000u, 46u}, // ali -> Latn
-    {0xB5600000u, 46u}, // aln -> Latn
+    {0x99200000u, 45u}, // ajg -> Latn
+    {0x616B0000u, 45u}, // ak -> Latn
+    {0xA9400000u, 100u}, // akk -> Xsux
+    {0x81600000u, 45u}, // ala -> Latn
+    {0xA1600000u, 45u}, // ali -> Latn
+    {0xB5600000u, 45u}, // aln -> Latn
     {0xCD600000u, 18u}, // alt -> Cyrl
     {0x616D0000u, 21u}, // am -> Ethi
-    {0xB1800000u, 46u}, // amm -> Latn
-    {0xB5800000u, 46u}, // amn -> Latn
-    {0xB9800000u, 46u}, // amo -> Latn
-    {0xBD800000u, 46u}, // amp -> Latn
-    {0x616E0000u, 46u}, // an -> Latn
-    {0x89A00000u, 46u}, // anc -> Latn
-    {0xA9A00000u, 46u}, // ank -> Latn
-    {0xB5A00000u, 46u}, // ann -> Latn
-    {0xE1A00000u, 46u}, // any -> Latn
-    {0xA5C00000u, 46u}, // aoj -> Latn
-    {0xB1C00000u, 46u}, // aom -> Latn
-    {0xE5C00000u, 46u}, // aoz -> Latn
+    {0xB1800000u, 45u}, // amm -> Latn
+    {0xB5800000u, 45u}, // amn -> Latn
+    {0xB9800000u, 45u}, // amo -> Latn
+    {0xBD800000u, 45u}, // amp -> Latn
+    {0x616E0000u, 45u}, // an -> Latn
+    {0x89A00000u, 45u}, // anc -> Latn
+    {0xA9A00000u, 45u}, // ank -> Latn
+    {0xB5A00000u, 45u}, // ann -> Latn
+    {0xE1A00000u, 45u}, // any -> Latn
+    {0xA5C00000u, 45u}, // aoj -> Latn
+    {0xB1C00000u, 45u}, // aom -> Latn
+    {0xE5C00000u, 45u}, // aoz -> Latn
     {0x89E00000u,  2u}, // apc -> Arab
     {0x8DE00000u,  2u}, // apd -> Arab
-    {0x91E00000u, 46u}, // ape -> Latn
-    {0xC5E00000u, 46u}, // apr -> Latn
-    {0xC9E00000u, 46u}, // aps -> Latn
-    {0xE5E00000u, 46u}, // apz -> Latn
+    {0x91E00000u, 45u}, // ape -> Latn
+    {0xC5E00000u, 45u}, // apr -> Latn
+    {0xC9E00000u, 45u}, // aps -> Latn
+    {0xE5E00000u, 45u}, // apz -> Latn
     {0x61720000u,  2u}, // ar -> Arab
-    {0x61725842u, 100u}, // ar-XB -> ~~~B
+    {0x61725842u, 103u}, // ar-XB -> ~~~B
     {0x8A200000u,  3u}, // arc -> Armi
-    {0x9E200000u, 46u}, // arh -> Latn
-    {0xB6200000u, 46u}, // arn -> Latn
-    {0xBA200000u, 46u}, // aro -> Latn
+    {0x9E200000u, 45u}, // arh -> Latn
+    {0xB6200000u, 45u}, // arn -> Latn
+    {0xBA200000u, 45u}, // aro -> Latn
     {0xC2200000u,  2u}, // arq -> Arab
     {0xCA200000u,  2u}, // ars -> Arab
     {0xE2200000u,  2u}, // ary -> Arab
     {0xE6200000u,  2u}, // arz -> Arab
     {0x61730000u,  8u}, // as -> Beng
-    {0x82400000u, 46u}, // asa -> Latn
-    {0x92400000u, 77u}, // ase -> Sgnw
-    {0x9A400000u, 46u}, // asg -> Latn
-    {0xBA400000u, 46u}, // aso -> Latn
-    {0xCE400000u, 46u}, // ast -> Latn
-    {0x82600000u, 46u}, // ata -> Latn
-    {0x9A600000u, 46u}, // atg -> Latn
-    {0xA6600000u, 46u}, // atj -> Latn
-    {0xE2800000u, 46u}, // auy -> Latn
+    {0x82400000u, 45u}, // asa -> Latn
+    {0x92400000u, 78u}, // ase -> Sgnw
+    {0x9A400000u, 45u}, // asg -> Latn
+    {0xBA400000u, 45u}, // aso -> Latn
+    {0xCE400000u, 45u}, // ast -> Latn
+    {0x82600000u, 45u}, // ata -> Latn
+    {0x9A600000u, 45u}, // atg -> Latn
+    {0xA6600000u, 45u}, // atj -> Latn
+    {0xE2800000u, 45u}, // auy -> Latn
     {0x61760000u, 18u}, // av -> Cyrl
     {0xAEA00000u,  2u}, // avl -> Arab
-    {0xB6A00000u, 46u}, // avn -> Latn
-    {0xCEA00000u, 46u}, // avt -> Latn
-    {0xD2A00000u, 46u}, // avu -> Latn
+    {0xB6A00000u, 45u}, // avn -> Latn
+    {0xCEA00000u, 45u}, // avt -> Latn
+    {0xD2A00000u, 45u}, // avu -> Latn
     {0x82C00000u, 19u}, // awa -> Deva
-    {0x86C00000u, 46u}, // awb -> Latn
-    {0xBAC00000u, 46u}, // awo -> Latn
-    {0xDEC00000u, 46u}, // awx -> Latn
-    {0x61790000u, 46u}, // ay -> Latn
-    {0x87000000u, 46u}, // ayb -> Latn
-    {0x617A0000u, 46u}, // az -> Latn
+    {0x86C00000u, 45u}, // awb -> Latn
+    {0xBAC00000u, 45u}, // awo -> Latn
+    {0xDEC00000u, 45u}, // awx -> Latn
+    {0x61790000u, 45u}, // ay -> Latn
+    {0x87000000u, 45u}, // ayb -> Latn
+    {0x617A0000u, 45u}, // az -> Latn
     {0x617A4951u,  2u}, // az-IQ -> Arab
     {0x617A4952u,  2u}, // az-IR -> Arab
     {0x617A5255u, 18u}, // az-RU -> Cyrl
     {0x62610000u, 18u}, // ba -> Cyrl
     {0xAC010000u,  2u}, // bal -> Arab
-    {0xB4010000u, 46u}, // ban -> Latn
+    {0xB4010000u, 45u}, // ban -> Latn
     {0xBC010000u, 19u}, // bap -> Deva
-    {0xC4010000u, 46u}, // bar -> Latn
-    {0xC8010000u, 46u}, // bas -> Latn
-    {0xD4010000u, 46u}, // bav -> Latn
+    {0xC4010000u, 45u}, // bar -> Latn
+    {0xC8010000u, 45u}, // bas -> Latn
+    {0xD4010000u, 45u}, // bav -> Latn
     {0xDC010000u,  6u}, // bax -> Bamu
-    {0x80210000u, 46u}, // bba -> Latn
-    {0x84210000u, 46u}, // bbb -> Latn
-    {0x88210000u, 46u}, // bbc -> Latn
-    {0x8C210000u, 46u}, // bbd -> Latn
-    {0xA4210000u, 46u}, // bbj -> Latn
-    {0xBC210000u, 46u}, // bbp -> Latn
-    {0xC4210000u, 46u}, // bbr -> Latn
-    {0x94410000u, 46u}, // bcf -> Latn
-    {0x9C410000u, 46u}, // bch -> Latn
-    {0xA0410000u, 46u}, // bci -> Latn
-    {0xB0410000u, 46u}, // bcm -> Latn
-    {0xB4410000u, 46u}, // bcn -> Latn
-    {0xB8410000u, 46u}, // bco -> Latn
+    {0x80210000u, 45u}, // bba -> Latn
+    {0x84210000u, 45u}, // bbb -> Latn
+    {0x88210000u, 45u}, // bbc -> Latn
+    {0x8C210000u, 45u}, // bbd -> Latn
+    {0xA4210000u, 45u}, // bbj -> Latn
+    {0xBC210000u, 45u}, // bbp -> Latn
+    {0xC4210000u, 45u}, // bbr -> Latn
+    {0x94410000u, 45u}, // bcf -> Latn
+    {0x9C410000u, 45u}, // bch -> Latn
+    {0xA0410000u, 45u}, // bci -> Latn
+    {0xB0410000u, 45u}, // bcm -> Latn
+    {0xB4410000u, 45u}, // bcn -> Latn
+    {0xB8410000u, 45u}, // bco -> Latn
     {0xC0410000u, 21u}, // bcq -> Ethi
-    {0xD0410000u, 46u}, // bcu -> Latn
-    {0x8C610000u, 46u}, // bdd -> Latn
+    {0xD0410000u, 45u}, // bcu -> Latn
+    {0x8C610000u, 45u}, // bdd -> Latn
     {0x62650000u, 18u}, // be -> Cyrl
-    {0x94810000u, 46u}, // bef -> Latn
-    {0x9C810000u, 46u}, // beh -> Latn
+    {0x94810000u, 45u}, // bef -> Latn
+    {0x9C810000u, 45u}, // beh -> Latn
     {0xA4810000u,  2u}, // bej -> Arab
-    {0xB0810000u, 46u}, // bem -> Latn
-    {0xCC810000u, 46u}, // bet -> Latn
-    {0xD8810000u, 46u}, // bew -> Latn
-    {0xDC810000u, 46u}, // bex -> Latn
-    {0xE4810000u, 46u}, // bez -> Latn
-    {0x8CA10000u, 46u}, // bfd -> Latn
-    {0xC0A10000u, 85u}, // bfq -> Taml
+    {0xB0810000u, 45u}, // bem -> Latn
+    {0xCC810000u, 45u}, // bet -> Latn
+    {0xD8810000u, 45u}, // bew -> Latn
+    {0xDC810000u, 45u}, // bex -> Latn
+    {0xE4810000u, 45u}, // bez -> Latn
+    {0x8CA10000u, 45u}, // bfd -> Latn
+    {0xC0A10000u, 86u}, // bfq -> Taml
     {0xCCA10000u,  2u}, // bft -> Arab
     {0xE0A10000u, 19u}, // bfy -> Deva
     {0x62670000u, 18u}, // bg -> Cyrl
@@ -241,1235 +244,1239 @@
     {0xB4C10000u,  2u}, // bgn -> Arab
     {0xDCC10000u, 26u}, // bgx -> Grek
     {0x84E10000u, 19u}, // bhb -> Deva
-    {0x98E10000u, 46u}, // bhg -> Latn
+    {0x98E10000u, 45u}, // bhg -> Latn
     {0xA0E10000u, 19u}, // bhi -> Deva
-    {0xACE10000u, 46u}, // bhl -> Latn
+    {0xACE10000u, 45u}, // bhl -> Latn
     {0xB8E10000u, 19u}, // bho -> Deva
-    {0xE0E10000u, 46u}, // bhy -> Latn
-    {0x62690000u, 46u}, // bi -> Latn
-    {0x85010000u, 46u}, // bib -> Latn
-    {0x99010000u, 46u}, // big -> Latn
-    {0xA9010000u, 46u}, // bik -> Latn
-    {0xB1010000u, 46u}, // bim -> Latn
-    {0xB5010000u, 46u}, // bin -> Latn
-    {0xB9010000u, 46u}, // bio -> Latn
-    {0xC1010000u, 46u}, // biq -> Latn
-    {0x9D210000u, 46u}, // bjh -> Latn
+    {0xE0E10000u, 45u}, // bhy -> Latn
+    {0x62690000u, 45u}, // bi -> Latn
+    {0x85010000u, 45u}, // bib -> Latn
+    {0x99010000u, 45u}, // big -> Latn
+    {0xA9010000u, 45u}, // bik -> Latn
+    {0xB1010000u, 45u}, // bim -> Latn
+    {0xB5010000u, 45u}, // bin -> Latn
+    {0xB9010000u, 45u}, // bio -> Latn
+    {0xC1010000u, 45u}, // biq -> Latn
+    {0x9D210000u, 45u}, // bjh -> Latn
     {0xA1210000u, 21u}, // bji -> Ethi
     {0xA5210000u, 19u}, // bjj -> Deva
-    {0xB5210000u, 46u}, // bjn -> Latn
-    {0xB9210000u, 46u}, // bjo -> Latn
-    {0xC5210000u, 46u}, // bjr -> Latn
-    {0xCD210000u, 46u}, // bjt -> Latn
-    {0xE5210000u, 46u}, // bjz -> Latn
-    {0x89410000u, 46u}, // bkc -> Latn
-    {0xB1410000u, 46u}, // bkm -> Latn
-    {0xC1410000u, 46u}, // bkq -> Latn
-    {0xD1410000u, 46u}, // bku -> Latn
-    {0xD5410000u, 46u}, // bkv -> Latn
-    {0xCD610000u, 87u}, // blt -> Tavt
-    {0x626D0000u, 46u}, // bm -> Latn
-    {0x9D810000u, 46u}, // bmh -> Latn
-    {0xA9810000u, 46u}, // bmk -> Latn
-    {0xC1810000u, 46u}, // bmq -> Latn
-    {0xD1810000u, 46u}, // bmu -> Latn
+    {0xB5210000u, 45u}, // bjn -> Latn
+    {0xB9210000u, 45u}, // bjo -> Latn
+    {0xC5210000u, 45u}, // bjr -> Latn
+    {0xCD210000u, 45u}, // bjt -> Latn
+    {0xE5210000u, 45u}, // bjz -> Latn
+    {0x89410000u, 45u}, // bkc -> Latn
+    {0xB1410000u, 45u}, // bkm -> Latn
+    {0xC1410000u, 45u}, // bkq -> Latn
+    {0xD1410000u, 45u}, // bku -> Latn
+    {0xD5410000u, 45u}, // bkv -> Latn
+    {0x99610000u, 45u}, // blg -> Latn
+    {0xCD610000u, 88u}, // blt -> Tavt
+    {0x626D0000u, 45u}, // bm -> Latn
+    {0x9D810000u, 45u}, // bmh -> Latn
+    {0xA9810000u, 45u}, // bmk -> Latn
+    {0xC1810000u, 45u}, // bmq -> Latn
+    {0xD1810000u, 45u}, // bmu -> Latn
     {0x626E0000u,  8u}, // bn -> Beng
-    {0x99A10000u, 46u}, // bng -> Latn
-    {0xB1A10000u, 46u}, // bnm -> Latn
-    {0xBDA10000u, 46u}, // bnp -> Latn
-    {0x626F0000u, 92u}, // bo -> Tibt
-    {0xA5C10000u, 46u}, // boj -> Latn
-    {0xB1C10000u, 46u}, // bom -> Latn
-    {0xB5C10000u, 46u}, // bon -> Latn
+    {0x99A10000u, 45u}, // bng -> Latn
+    {0xB1A10000u, 45u}, // bnm -> Latn
+    {0xBDA10000u, 45u}, // bnp -> Latn
+    {0x626F0000u, 93u}, // bo -> Tibt
+    {0xA5C10000u, 45u}, // boj -> Latn
+    {0xB1C10000u, 45u}, // bom -> Latn
+    {0xB5C10000u, 45u}, // bon -> Latn
     {0xE1E10000u,  8u}, // bpy -> Beng
-    {0x8A010000u, 46u}, // bqc -> Latn
+    {0x8A010000u, 45u}, // bqc -> Latn
     {0xA2010000u,  2u}, // bqi -> Arab
-    {0xBE010000u, 46u}, // bqp -> Latn
-    {0xD6010000u, 46u}, // bqv -> Latn
-    {0x62720000u, 46u}, // br -> Latn
+    {0xBE010000u, 45u}, // bqp -> Latn
+    {0xD6010000u, 45u}, // bqv -> Latn
+    {0x62720000u, 45u}, // br -> Latn
     {0x82210000u, 19u}, // bra -> Deva
     {0x9E210000u,  2u}, // brh -> Arab
     {0xDE210000u, 19u}, // brx -> Deva
-    {0xE6210000u, 46u}, // brz -> Latn
-    {0x62730000u, 46u}, // bs -> Latn
-    {0xA6410000u, 46u}, // bsj -> Latn
+    {0xE6210000u, 45u}, // brz -> Latn
+    {0x62730000u, 45u}, // bs -> Latn
+    {0xA6410000u, 45u}, // bsj -> Latn
     {0xC2410000u,  7u}, // bsq -> Bass
-    {0xCA410000u, 46u}, // bss -> Latn
+    {0xCA410000u, 45u}, // bss -> Latn
     {0xCE410000u, 21u}, // bst -> Ethi
-    {0xBA610000u, 46u}, // bto -> Latn
-    {0xCE610000u, 46u}, // btt -> Latn
+    {0xBA610000u, 45u}, // bto -> Latn
+    {0xCE610000u, 45u}, // btt -> Latn
     {0xD6610000u, 19u}, // btv -> Deva
     {0x82810000u, 18u}, // bua -> Cyrl
-    {0x8A810000u, 46u}, // buc -> Latn
-    {0x8E810000u, 46u}, // bud -> Latn
-    {0x9A810000u, 46u}, // bug -> Latn
-    {0xAA810000u, 46u}, // buk -> Latn
-    {0xB2810000u, 46u}, // bum -> Latn
-    {0xBA810000u, 46u}, // buo -> Latn
-    {0xCA810000u, 46u}, // bus -> Latn
-    {0xD2810000u, 46u}, // buu -> Latn
-    {0x86A10000u, 46u}, // bvb -> Latn
-    {0x8EC10000u, 46u}, // bwd -> Latn
-    {0xC6C10000u, 46u}, // bwr -> Latn
-    {0x9EE10000u, 46u}, // bxh -> Latn
-    {0x93010000u, 46u}, // bye -> Latn
+    {0x8A810000u, 45u}, // buc -> Latn
+    {0x8E810000u, 45u}, // bud -> Latn
+    {0x9A810000u, 45u}, // bug -> Latn
+    {0xAA810000u, 45u}, // buk -> Latn
+    {0xB2810000u, 45u}, // bum -> Latn
+    {0xBA810000u, 45u}, // buo -> Latn
+    {0xCA810000u, 45u}, // bus -> Latn
+    {0xD2810000u, 45u}, // buu -> Latn
+    {0x86A10000u, 45u}, // bvb -> Latn
+    {0x8EC10000u, 45u}, // bwd -> Latn
+    {0xC6C10000u, 45u}, // bwr -> Latn
+    {0x9EE10000u, 45u}, // bxh -> Latn
+    {0x93010000u, 45u}, // bye -> Latn
     {0xB7010000u, 21u}, // byn -> Ethi
-    {0xC7010000u, 46u}, // byr -> Latn
-    {0xCB010000u, 46u}, // bys -> Latn
-    {0xD7010000u, 46u}, // byv -> Latn
-    {0xDF010000u, 46u}, // byx -> Latn
-    {0x83210000u, 46u}, // bza -> Latn
-    {0x93210000u, 46u}, // bze -> Latn
-    {0x97210000u, 46u}, // bzf -> Latn
-    {0x9F210000u, 46u}, // bzh -> Latn
-    {0xDB210000u, 46u}, // bzw -> Latn
-    {0x63610000u, 46u}, // ca -> Latn
-    {0x8C020000u, 46u}, // cad -> Latn
-    {0xB4020000u, 46u}, // can -> Latn
-    {0xA4220000u, 46u}, // cbj -> Latn
-    {0x9C420000u, 46u}, // cch -> Latn
+    {0xC7010000u, 45u}, // byr -> Latn
+    {0xCB010000u, 45u}, // bys -> Latn
+    {0xD7010000u, 45u}, // byv -> Latn
+    {0xDF010000u, 45u}, // byx -> Latn
+    {0x83210000u, 45u}, // bza -> Latn
+    {0x93210000u, 45u}, // bze -> Latn
+    {0x97210000u, 45u}, // bzf -> Latn
+    {0x9F210000u, 45u}, // bzh -> Latn
+    {0xDB210000u, 45u}, // bzw -> Latn
+    {0x63610000u, 45u}, // ca -> Latn
+    {0x8C020000u, 45u}, // cad -> Latn
+    {0xB4020000u, 45u}, // can -> Latn
+    {0xA4220000u, 45u}, // cbj -> Latn
+    {0x9C420000u, 45u}, // cch -> Latn
     {0xBC420000u, 10u}, // ccp -> Cakm
     {0x63650000u, 18u}, // ce -> Cyrl
-    {0x84820000u, 46u}, // ceb -> Latn
-    {0x80A20000u, 46u}, // cfa -> Latn
-    {0x98C20000u, 46u}, // cgg -> Latn
-    {0x63680000u, 46u}, // ch -> Latn
-    {0xA8E20000u, 46u}, // chk -> Latn
+    {0x84820000u, 45u}, // ceb -> Latn
+    {0x80A20000u, 45u}, // cfa -> Latn
+    {0x98C20000u, 45u}, // cgg -> Latn
+    {0x63680000u, 45u}, // ch -> Latn
+    {0xA8E20000u, 45u}, // chk -> Latn
     {0xB0E20000u, 18u}, // chm -> Cyrl
-    {0xB8E20000u, 46u}, // cho -> Latn
-    {0xBCE20000u, 46u}, // chp -> Latn
+    {0xB8E20000u, 45u}, // cho -> Latn
+    {0xBCE20000u, 45u}, // chp -> Latn
     {0xC4E20000u, 14u}, // chr -> Cher
-    {0x89020000u, 46u}, // cic -> Latn
+    {0x89020000u, 45u}, // cic -> Latn
     {0x81220000u,  2u}, // cja -> Arab
     {0xB1220000u, 13u}, // cjm -> Cham
-    {0xD5220000u, 46u}, // cjv -> Latn
+    {0xD5220000u, 45u}, // cjv -> Latn
     {0x85420000u,  2u}, // ckb -> Arab
-    {0xAD420000u, 46u}, // ckl -> Latn
-    {0xB9420000u, 46u}, // cko -> Latn
-    {0xE1420000u, 46u}, // cky -> Latn
-    {0x81620000u, 46u}, // cla -> Latn
-    {0x91820000u, 46u}, // cme -> Latn
-    {0x99820000u, 81u}, // cmg -> Soyo
-    {0x636F0000u, 46u}, // co -> Latn
+    {0xAD420000u, 45u}, // ckl -> Latn
+    {0xB9420000u, 45u}, // cko -> Latn
+    {0xE1420000u, 45u}, // cky -> Latn
+    {0x81620000u, 45u}, // cla -> Latn
+    {0x91820000u, 45u}, // cme -> Latn
+    {0x99820000u, 82u}, // cmg -> Soyo
+    {0x636F0000u, 45u}, // co -> Latn
     {0xBDC20000u, 16u}, // cop -> Copt
-    {0xC9E20000u, 46u}, // cps -> Latn
+    {0xC9E20000u, 45u}, // cps -> Latn
     {0x63720000u, 11u}, // cr -> Cans
     {0x9E220000u, 18u}, // crh -> Cyrl
     {0xA6220000u, 11u}, // crj -> Cans
     {0xAA220000u, 11u}, // crk -> Cans
     {0xAE220000u, 11u}, // crl -> Cans
     {0xB2220000u, 11u}, // crm -> Cans
-    {0xCA220000u, 46u}, // crs -> Latn
-    {0x63730000u, 46u}, // cs -> Latn
-    {0x86420000u, 46u}, // csb -> Latn
+    {0xCA220000u, 45u}, // crs -> Latn
+    {0x63730000u, 45u}, // cs -> Latn
+    {0x86420000u, 45u}, // csb -> Latn
     {0xDA420000u, 11u}, // csw -> Cans
     {0x8E620000u, 68u}, // ctd -> Pauc
     {0x63750000u, 18u}, // cu -> Cyrl
     {0x63760000u, 18u}, // cv -> Cyrl
-    {0x63790000u, 46u}, // cy -> Latn
-    {0x64610000u, 46u}, // da -> Latn
-    {0x8C030000u, 46u}, // dad -> Latn
-    {0x94030000u, 46u}, // daf -> Latn
-    {0x98030000u, 46u}, // dag -> Latn
-    {0x9C030000u, 46u}, // dah -> Latn
-    {0xA8030000u, 46u}, // dak -> Latn
+    {0x63790000u, 45u}, // cy -> Latn
+    {0x64610000u, 45u}, // da -> Latn
+    {0x8C030000u, 45u}, // dad -> Latn
+    {0x94030000u, 45u}, // daf -> Latn
+    {0x98030000u, 45u}, // dag -> Latn
+    {0x9C030000u, 45u}, // dah -> Latn
+    {0xA8030000u, 45u}, // dak -> Latn
     {0xC4030000u, 18u}, // dar -> Cyrl
-    {0xD4030000u, 46u}, // dav -> Latn
-    {0x8C230000u, 46u}, // dbd -> Latn
-    {0xC0230000u, 46u}, // dbq -> Latn
+    {0xD4030000u, 45u}, // dav -> Latn
+    {0x8C230000u, 45u}, // dbd -> Latn
+    {0xC0230000u, 45u}, // dbq -> Latn
     {0x88430000u,  2u}, // dcc -> Arab
-    {0xB4630000u, 46u}, // ddn -> Latn
-    {0x64650000u, 46u}, // de -> Latn
-    {0x8C830000u, 46u}, // ded -> Latn
-    {0xB4830000u, 46u}, // den -> Latn
-    {0x80C30000u, 46u}, // dga -> Latn
-    {0x9CC30000u, 46u}, // dgh -> Latn
-    {0xA0C30000u, 46u}, // dgi -> Latn
+    {0xB4630000u, 45u}, // ddn -> Latn
+    {0x64650000u, 45u}, // de -> Latn
+    {0x8C830000u, 45u}, // ded -> Latn
+    {0xB4830000u, 45u}, // den -> Latn
+    {0x80C30000u, 45u}, // dga -> Latn
+    {0x9CC30000u, 45u}, // dgh -> Latn
+    {0xA0C30000u, 45u}, // dgi -> Latn
     {0xACC30000u,  2u}, // dgl -> Arab
-    {0xC4C30000u, 46u}, // dgr -> Latn
-    {0xE4C30000u, 46u}, // dgz -> Latn
-    {0x81030000u, 46u}, // dia -> Latn
-    {0x91230000u, 46u}, // dje -> Latn
-    {0x95830000u, 54u}, // dmf -> Medf
-    {0xA5A30000u, 46u}, // dnj -> Latn
-    {0x85C30000u, 46u}, // dob -> Latn
+    {0xC4C30000u, 45u}, // dgr -> Latn
+    {0xE4C30000u, 45u}, // dgz -> Latn
+    {0x81030000u, 45u}, // dia -> Latn
+    {0x91230000u, 45u}, // dje -> Latn
+    {0x95830000u, 53u}, // dmf -> Medf
+    {0xA5A30000u, 45u}, // dnj -> Latn
+    {0x85C30000u, 45u}, // dob -> Latn
     {0xA1C30000u, 19u}, // doi -> Deva
-    {0xBDC30000u, 46u}, // dop -> Latn
-    {0xD9C30000u, 46u}, // dow -> Latn
-    {0x9E230000u, 57u}, // drh -> Mong
-    {0xA2230000u, 46u}, // dri -> Latn
+    {0xBDC30000u, 45u}, // dop -> Latn
+    {0xD9C30000u, 45u}, // dow -> Latn
+    {0x9E230000u, 56u}, // drh -> Mong
+    {0xA2230000u, 45u}, // dri -> Latn
     {0xCA230000u, 21u}, // drs -> Ethi
-    {0x86430000u, 46u}, // dsb -> Latn
-    {0xB2630000u, 46u}, // dtm -> Latn
-    {0xBE630000u, 46u}, // dtp -> Latn
-    {0xCA630000u, 46u}, // dts -> Latn
+    {0x86430000u, 45u}, // dsb -> Latn
+    {0xB2630000u, 45u}, // dtm -> Latn
+    {0xBE630000u, 45u}, // dtp -> Latn
+    {0xCA630000u, 45u}, // dts -> Latn
     {0xE2630000u, 19u}, // dty -> Deva
-    {0x82830000u, 46u}, // dua -> Latn
-    {0x8A830000u, 46u}, // duc -> Latn
-    {0x8E830000u, 46u}, // dud -> Latn
-    {0x9A830000u, 46u}, // dug -> Latn
-    {0x64760000u, 90u}, // dv -> Thaa
-    {0x82A30000u, 46u}, // dva -> Latn
-    {0xDAC30000u, 46u}, // dww -> Latn
-    {0xBB030000u, 46u}, // dyo -> Latn
-    {0xD3030000u, 46u}, // dyu -> Latn
-    {0x647A0000u, 92u}, // dz -> Tibt
-    {0x9B230000u, 46u}, // dzg -> Latn
-    {0xD0240000u, 46u}, // ebu -> Latn
-    {0x65650000u, 46u}, // ee -> Latn
-    {0xA0A40000u, 46u}, // efi -> Latn
-    {0xACC40000u, 46u}, // egl -> Latn
+    {0x82830000u, 45u}, // dua -> Latn
+    {0x8A830000u, 45u}, // duc -> Latn
+    {0x8E830000u, 45u}, // dud -> Latn
+    {0x9A830000u, 45u}, // dug -> Latn
+    {0x64760000u, 91u}, // dv -> Thaa
+    {0x82A30000u, 45u}, // dva -> Latn
+    {0xDAC30000u, 45u}, // dww -> Latn
+    {0xBB030000u, 45u}, // dyo -> Latn
+    {0xD3030000u, 45u}, // dyu -> Latn
+    {0x647A0000u, 93u}, // dz -> Tibt
+    {0x9B230000u, 45u}, // dzg -> Latn
+    {0xD0240000u, 45u}, // ebu -> Latn
+    {0x65650000u, 45u}, // ee -> Latn
+    {0xA0A40000u, 45u}, // efi -> Latn
+    {0xACC40000u, 45u}, // egl -> Latn
     {0xE0C40000u, 20u}, // egy -> Egyp
-    {0x81440000u, 46u}, // eka -> Latn
-    {0xE1440000u, 37u}, // eky -> Kali
+    {0x81440000u, 45u}, // eka -> Latn
+    {0xE1440000u, 36u}, // eky -> Kali
     {0x656C0000u, 26u}, // el -> Grek
-    {0x81840000u, 46u}, // ema -> Latn
-    {0xA1840000u, 46u}, // emi -> Latn
-    {0x656E0000u, 46u}, // en -> Latn
-    {0x656E5841u, 99u}, // en-XA -> ~~~A
-    {0xB5A40000u, 46u}, // enn -> Latn
-    {0xC1A40000u, 46u}, // enq -> Latn
-    {0x656F0000u, 46u}, // eo -> Latn
-    {0xA2240000u, 46u}, // eri -> Latn
-    {0x65730000u, 46u}, // es -> Latn
+    {0x81840000u, 45u}, // ema -> Latn
+    {0xA1840000u, 45u}, // emi -> Latn
+    {0x656E0000u, 45u}, // en -> Latn
+    {0x656E5841u, 102u}, // en-XA -> ~~~A
+    {0xB5A40000u, 45u}, // enn -> Latn
+    {0xC1A40000u, 45u}, // enq -> Latn
+    {0x656F0000u, 45u}, // eo -> Latn
+    {0xA2240000u, 45u}, // eri -> Latn
+    {0x65730000u, 45u}, // es -> Latn
     {0x9A440000u, 24u}, // esg -> Gonm
-    {0xD2440000u, 46u}, // esu -> Latn
-    {0x65740000u, 46u}, // et -> Latn
-    {0xC6640000u, 46u}, // etr -> Latn
-    {0xCE640000u, 35u}, // ett -> Ital
-    {0xD2640000u, 46u}, // etu -> Latn
-    {0xDE640000u, 46u}, // etx -> Latn
-    {0x65750000u, 46u}, // eu -> Latn
-    {0xBAC40000u, 46u}, // ewo -> Latn
-    {0xCEE40000u, 46u}, // ext -> Latn
-    {0x83240000u, 46u}, // eza -> Latn
+    {0xD2440000u, 45u}, // esu -> Latn
+    {0x65740000u, 45u}, // et -> Latn
+    {0xC6640000u, 45u}, // etr -> Latn
+    {0xCE640000u, 34u}, // ett -> Ital
+    {0xD2640000u, 45u}, // etu -> Latn
+    {0xDE640000u, 45u}, // etx -> Latn
+    {0x65750000u, 45u}, // eu -> Latn
+    {0xBAC40000u, 45u}, // ewo -> Latn
+    {0xCEE40000u, 45u}, // ext -> Latn
+    {0x83240000u, 45u}, // eza -> Latn
     {0x66610000u,  2u}, // fa -> Arab
-    {0x80050000u, 46u}, // faa -> Latn
-    {0x84050000u, 46u}, // fab -> Latn
-    {0x98050000u, 46u}, // fag -> Latn
-    {0xA0050000u, 46u}, // fai -> Latn
-    {0xB4050000u, 46u}, // fan -> Latn
-    {0x66660000u, 46u}, // ff -> Latn
-    {0xA0A50000u, 46u}, // ffi -> Latn
-    {0xB0A50000u, 46u}, // ffm -> Latn
-    {0x66690000u, 46u}, // fi -> Latn
+    {0x80050000u, 45u}, // faa -> Latn
+    {0x84050000u, 45u}, // fab -> Latn
+    {0x98050000u, 45u}, // fag -> Latn
+    {0xA0050000u, 45u}, // fai -> Latn
+    {0xB4050000u, 45u}, // fan -> Latn
+    {0x66660000u, 45u}, // ff -> Latn
+    {0xA0A50000u, 45u}, // ffi -> Latn
+    {0xB0A50000u, 45u}, // ffm -> Latn
+    {0x66690000u, 45u}, // fi -> Latn
     {0x81050000u,  2u}, // fia -> Arab
-    {0xAD050000u, 46u}, // fil -> Latn
-    {0xCD050000u, 46u}, // fit -> Latn
-    {0x666A0000u, 46u}, // fj -> Latn
-    {0xC5650000u, 46u}, // flr -> Latn
-    {0xBD850000u, 46u}, // fmp -> Latn
-    {0x666F0000u, 46u}, // fo -> Latn
-    {0x8DC50000u, 46u}, // fod -> Latn
-    {0xB5C50000u, 46u}, // fon -> Latn
-    {0xC5C50000u, 46u}, // for -> Latn
-    {0x91E50000u, 46u}, // fpe -> Latn
-    {0xCA050000u, 46u}, // fqs -> Latn
-    {0x66720000u, 46u}, // fr -> Latn
-    {0x8A250000u, 46u}, // frc -> Latn
-    {0xBE250000u, 46u}, // frp -> Latn
-    {0xC6250000u, 46u}, // frr -> Latn
-    {0xCA250000u, 46u}, // frs -> Latn
+    {0xAD050000u, 45u}, // fil -> Latn
+    {0xCD050000u, 45u}, // fit -> Latn
+    {0x666A0000u, 45u}, // fj -> Latn
+    {0xC5650000u, 45u}, // flr -> Latn
+    {0xBD850000u, 45u}, // fmp -> Latn
+    {0x666F0000u, 45u}, // fo -> Latn
+    {0x8DC50000u, 45u}, // fod -> Latn
+    {0xB5C50000u, 45u}, // fon -> Latn
+    {0xC5C50000u, 45u}, // for -> Latn
+    {0x91E50000u, 45u}, // fpe -> Latn
+    {0xCA050000u, 45u}, // fqs -> Latn
+    {0x66720000u, 45u}, // fr -> Latn
+    {0x8A250000u, 45u}, // frc -> Latn
+    {0xBE250000u, 45u}, // frp -> Latn
+    {0xC6250000u, 45u}, // frr -> Latn
+    {0xCA250000u, 45u}, // frs -> Latn
     {0x86850000u,  2u}, // fub -> Arab
-    {0x8E850000u, 46u}, // fud -> Latn
-    {0x92850000u, 46u}, // fue -> Latn
-    {0x96850000u, 46u}, // fuf -> Latn
-    {0x9E850000u, 46u}, // fuh -> Latn
-    {0xC2850000u, 46u}, // fuq -> Latn
-    {0xC6850000u, 46u}, // fur -> Latn
-    {0xD6850000u, 46u}, // fuv -> Latn
-    {0xE2850000u, 46u}, // fuy -> Latn
-    {0xC6A50000u, 46u}, // fvr -> Latn
-    {0x66790000u, 46u}, // fy -> Latn
-    {0x67610000u, 46u}, // ga -> Latn
-    {0x80060000u, 46u}, // gaa -> Latn
-    {0x94060000u, 46u}, // gaf -> Latn
-    {0x98060000u, 46u}, // gag -> Latn
-    {0x9C060000u, 46u}, // gah -> Latn
-    {0xA4060000u, 46u}, // gaj -> Latn
-    {0xB0060000u, 46u}, // gam -> Latn
+    {0x8E850000u, 45u}, // fud -> Latn
+    {0x92850000u, 45u}, // fue -> Latn
+    {0x96850000u, 45u}, // fuf -> Latn
+    {0x9E850000u, 45u}, // fuh -> Latn
+    {0xC2850000u, 45u}, // fuq -> Latn
+    {0xC6850000u, 45u}, // fur -> Latn
+    {0xD6850000u, 45u}, // fuv -> Latn
+    {0xE2850000u, 45u}, // fuy -> Latn
+    {0xC6A50000u, 45u}, // fvr -> Latn
+    {0x66790000u, 45u}, // fy -> Latn
+    {0x67610000u, 45u}, // ga -> Latn
+    {0x80060000u, 45u}, // gaa -> Latn
+    {0x94060000u, 45u}, // gaf -> Latn
+    {0x98060000u, 45u}, // gag -> Latn
+    {0x9C060000u, 45u}, // gah -> Latn
+    {0xA4060000u, 45u}, // gaj -> Latn
+    {0xB0060000u, 45u}, // gam -> Latn
     {0xB4060000u, 29u}, // gan -> Hans
-    {0xD8060000u, 46u}, // gaw -> Latn
-    {0xE0060000u, 46u}, // gay -> Latn
-    {0x80260000u, 46u}, // gba -> Latn
-    {0x94260000u, 46u}, // gbf -> Latn
+    {0xD8060000u, 45u}, // gaw -> Latn
+    {0xE0060000u, 45u}, // gay -> Latn
+    {0x80260000u, 45u}, // gba -> Latn
+    {0x94260000u, 45u}, // gbf -> Latn
     {0xB0260000u, 19u}, // gbm -> Deva
-    {0xE0260000u, 46u}, // gby -> Latn
+    {0xE0260000u, 45u}, // gby -> Latn
     {0xE4260000u,  2u}, // gbz -> Arab
-    {0xC4460000u, 46u}, // gcr -> Latn
-    {0x67640000u, 46u}, // gd -> Latn
-    {0x90660000u, 46u}, // gde -> Latn
-    {0xB4660000u, 46u}, // gdn -> Latn
-    {0xC4660000u, 46u}, // gdr -> Latn
-    {0x84860000u, 46u}, // geb -> Latn
-    {0xA4860000u, 46u}, // gej -> Latn
-    {0xAC860000u, 46u}, // gel -> Latn
+    {0xC4460000u, 45u}, // gcr -> Latn
+    {0x67640000u, 45u}, // gd -> Latn
+    {0x90660000u, 45u}, // gde -> Latn
+    {0xB4660000u, 45u}, // gdn -> Latn
+    {0xC4660000u, 45u}, // gdr -> Latn
+    {0x84860000u, 45u}, // geb -> Latn
+    {0xA4860000u, 45u}, // gej -> Latn
+    {0xAC860000u, 45u}, // gel -> Latn
     {0xE4860000u, 21u}, // gez -> Ethi
-    {0xA8A60000u, 46u}, // gfk -> Latn
+    {0xA8A60000u, 45u}, // gfk -> Latn
     {0xB4C60000u, 19u}, // ggn -> Deva
-    {0xC8E60000u, 46u}, // ghs -> Latn
-    {0xAD060000u, 46u}, // gil -> Latn
-    {0xB1060000u, 46u}, // gim -> Latn
+    {0xC8E60000u, 45u}, // ghs -> Latn
+    {0xAD060000u, 45u}, // gil -> Latn
+    {0xB1060000u, 45u}, // gim -> Latn
     {0xA9260000u,  2u}, // gjk -> Arab
-    {0xB5260000u, 46u}, // gjn -> Latn
+    {0xB5260000u, 45u}, // gjn -> Latn
     {0xD1260000u,  2u}, // gju -> Arab
-    {0xB5460000u, 46u}, // gkn -> Latn
-    {0xBD460000u, 46u}, // gkp -> Latn
-    {0x676C0000u, 46u}, // gl -> Latn
+    {0xB5460000u, 45u}, // gkn -> Latn
+    {0xBD460000u, 45u}, // gkp -> Latn
+    {0x676C0000u, 45u}, // gl -> Latn
     {0xA9660000u,  2u}, // glk -> Arab
-    {0xB1860000u, 46u}, // gmm -> Latn
+    {0xB1860000u, 45u}, // gmm -> Latn
     {0xD5860000u, 21u}, // gmv -> Ethi
-    {0x676E0000u, 46u}, // gn -> Latn
-    {0x8DA60000u, 46u}, // gnd -> Latn
-    {0x99A60000u, 46u}, // gng -> Latn
-    {0x8DC60000u, 46u}, // god -> Latn
+    {0x676E0000u, 45u}, // gn -> Latn
+    {0x8DA60000u, 45u}, // gnd -> Latn
+    {0x99A60000u, 45u}, // gng -> Latn
+    {0x8DC60000u, 45u}, // god -> Latn
     {0x95C60000u, 21u}, // gof -> Ethi
-    {0xA1C60000u, 46u}, // goi -> Latn
+    {0xA1C60000u, 45u}, // goi -> Latn
     {0xB1C60000u, 19u}, // gom -> Deva
-    {0xB5C60000u, 88u}, // gon -> Telu
-    {0xC5C60000u, 46u}, // gor -> Latn
-    {0xC9C60000u, 46u}, // gos -> Latn
+    {0xB5C60000u, 89u}, // gon -> Telu
+    {0xC5C60000u, 45u}, // gor -> Latn
+    {0xC9C60000u, 45u}, // gos -> Latn
     {0xCDC60000u, 25u}, // got -> Goth
-    {0x86260000u, 46u}, // grb -> Latn
+    {0x86260000u, 45u}, // grb -> Latn
     {0x8A260000u, 17u}, // grc -> Cprt
     {0xCE260000u,  8u}, // grt -> Beng
-    {0xDA260000u, 46u}, // grw -> Latn
-    {0xDA460000u, 46u}, // gsw -> Latn
+    {0xDA260000u, 45u}, // grw -> Latn
+    {0xDA460000u, 45u}, // gsw -> Latn
     {0x67750000u, 27u}, // gu -> Gujr
-    {0x86860000u, 46u}, // gub -> Latn
-    {0x8A860000u, 46u}, // guc -> Latn
-    {0x8E860000u, 46u}, // gud -> Latn
-    {0xC6860000u, 46u}, // gur -> Latn
-    {0xDA860000u, 46u}, // guw -> Latn
-    {0xDE860000u, 46u}, // gux -> Latn
-    {0xE6860000u, 46u}, // guz -> Latn
-    {0x67760000u, 46u}, // gv -> Latn
-    {0x96A60000u, 46u}, // gvf -> Latn
+    {0x86860000u, 45u}, // gub -> Latn
+    {0x8A860000u, 45u}, // guc -> Latn
+    {0x8E860000u, 45u}, // gud -> Latn
+    {0xC6860000u, 45u}, // gur -> Latn
+    {0xDA860000u, 45u}, // guw -> Latn
+    {0xDE860000u, 45u}, // gux -> Latn
+    {0xE6860000u, 45u}, // guz -> Latn
+    {0x67760000u, 45u}, // gv -> Latn
+    {0x96A60000u, 45u}, // gvf -> Latn
     {0xC6A60000u, 19u}, // gvr -> Deva
-    {0xCAA60000u, 46u}, // gvs -> Latn
+    {0xCAA60000u, 45u}, // gvs -> Latn
     {0x8AC60000u,  2u}, // gwc -> Arab
-    {0xA2C60000u, 46u}, // gwi -> Latn
+    {0xA2C60000u, 45u}, // gwi -> Latn
     {0xCEC60000u,  2u}, // gwt -> Arab
-    {0xA3060000u, 46u}, // gyi -> Latn
-    {0x68610000u, 46u}, // ha -> Latn
+    {0xA3060000u, 45u}, // gyi -> Latn
+    {0x68610000u, 45u}, // ha -> Latn
     {0x6861434Du,  2u}, // ha-CM -> Arab
     {0x68615344u,  2u}, // ha-SD -> Arab
-    {0x98070000u, 46u}, // hag -> Latn
+    {0x98070000u, 45u}, // hag -> Latn
     {0xA8070000u, 29u}, // hak -> Hans
-    {0xB0070000u, 46u}, // ham -> Latn
-    {0xD8070000u, 46u}, // haw -> Latn
+    {0xB0070000u, 45u}, // ham -> Latn
+    {0xD8070000u, 45u}, // haw -> Latn
     {0xE4070000u,  2u}, // haz -> Arab
-    {0x84270000u, 46u}, // hbb -> Latn
+    {0x84270000u, 45u}, // hbb -> Latn
     {0xE0670000u, 21u}, // hdy -> Ethi
     {0x68650000u, 31u}, // he -> Hebr
-    {0xE0E70000u, 46u}, // hhy -> Latn
+    {0xE0E70000u, 45u}, // hhy -> Latn
     {0x68690000u, 19u}, // hi -> Deva
-    {0x81070000u, 46u}, // hia -> Latn
-    {0x95070000u, 46u}, // hif -> Latn
-    {0x99070000u, 46u}, // hig -> Latn
-    {0x9D070000u, 46u}, // hih -> Latn
-    {0xAD070000u, 46u}, // hil -> Latn
-    {0x81670000u, 46u}, // hla -> Latn
+    {0x81070000u, 45u}, // hia -> Latn
+    {0x95070000u, 45u}, // hif -> Latn
+    {0x99070000u, 45u}, // hig -> Latn
+    {0x9D070000u, 45u}, // hih -> Latn
+    {0xAD070000u, 45u}, // hil -> Latn
+    {0x81670000u, 45u}, // hla -> Latn
     {0xD1670000u, 32u}, // hlu -> Hluw
     {0x8D870000u, 71u}, // hmd -> Plrd
-    {0xCD870000u, 46u}, // hmt -> Latn
+    {0xCD870000u, 45u}, // hmt -> Latn
     {0x8DA70000u,  2u}, // hnd -> Arab
     {0x91A70000u, 19u}, // hne -> Deva
-    {0xA5A70000u, 33u}, // hnj -> Hmng
-    {0xB5A70000u, 46u}, // hnn -> Latn
+    {0xA5A70000u, 33u}, // hnj -> Hmnp
+    {0xB5A70000u, 45u}, // hnn -> Latn
     {0xB9A70000u,  2u}, // hno -> Arab
-    {0x686F0000u, 46u}, // ho -> Latn
+    {0x686F0000u, 45u}, // ho -> Latn
     {0x89C70000u, 19u}, // hoc -> Deva
     {0xA5C70000u, 19u}, // hoj -> Deva
-    {0xCDC70000u, 46u}, // hot -> Latn
-    {0x68720000u, 46u}, // hr -> Latn
-    {0x86470000u, 46u}, // hsb -> Latn
+    {0xCDC70000u, 45u}, // hot -> Latn
+    {0x68720000u, 45u}, // hr -> Latn
+    {0x86470000u, 45u}, // hsb -> Latn
     {0xB6470000u, 29u}, // hsn -> Hans
-    {0x68740000u, 46u}, // ht -> Latn
-    {0x68750000u, 46u}, // hu -> Latn
-    {0xA2870000u, 46u}, // hui -> Latn
+    {0x68740000u, 45u}, // ht -> Latn
+    {0x68750000u, 45u}, // hu -> Latn
+    {0xA2870000u, 45u}, // hui -> Latn
     {0x68790000u,  4u}, // hy -> Armn
-    {0x687A0000u, 46u}, // hz -> Latn
-    {0x69610000u, 46u}, // ia -> Latn
-    {0xB4080000u, 46u}, // ian -> Latn
-    {0xC4080000u, 46u}, // iar -> Latn
-    {0x80280000u, 46u}, // iba -> Latn
-    {0x84280000u, 46u}, // ibb -> Latn
-    {0xE0280000u, 46u}, // iby -> Latn
-    {0x80480000u, 46u}, // ica -> Latn
-    {0x9C480000u, 46u}, // ich -> Latn
-    {0x69640000u, 46u}, // id -> Latn
-    {0x8C680000u, 46u}, // idd -> Latn
-    {0xA0680000u, 46u}, // idi -> Latn
-    {0xD0680000u, 46u}, // idu -> Latn
-    {0x90A80000u, 46u}, // ife -> Latn
-    {0x69670000u, 46u}, // ig -> Latn
-    {0x84C80000u, 46u}, // igb -> Latn
-    {0x90C80000u, 46u}, // ige -> Latn
-    {0x69690000u, 98u}, // ii -> Yiii
-    {0xA5280000u, 46u}, // ijj -> Latn
-    {0x696B0000u, 46u}, // ik -> Latn
-    {0xA9480000u, 46u}, // ikk -> Latn
-    {0xCD480000u, 46u}, // ikt -> Latn
-    {0xD9480000u, 46u}, // ikw -> Latn
-    {0xDD480000u, 46u}, // ikx -> Latn
-    {0xB9680000u, 46u}, // ilo -> Latn
-    {0xB9880000u, 46u}, // imo -> Latn
-    {0x696E0000u, 46u}, // in -> Latn
+    {0x687A0000u, 45u}, // hz -> Latn
+    {0x69610000u, 45u}, // ia -> Latn
+    {0xB4080000u, 45u}, // ian -> Latn
+    {0xC4080000u, 45u}, // iar -> Latn
+    {0x80280000u, 45u}, // iba -> Latn
+    {0x84280000u, 45u}, // ibb -> Latn
+    {0xE0280000u, 45u}, // iby -> Latn
+    {0x80480000u, 45u}, // ica -> Latn
+    {0x9C480000u, 45u}, // ich -> Latn
+    {0x69640000u, 45u}, // id -> Latn
+    {0x8C680000u, 45u}, // idd -> Latn
+    {0xA0680000u, 45u}, // idi -> Latn
+    {0xD0680000u, 45u}, // idu -> Latn
+    {0x90A80000u, 45u}, // ife -> Latn
+    {0x69670000u, 45u}, // ig -> Latn
+    {0x84C80000u, 45u}, // igb -> Latn
+    {0x90C80000u, 45u}, // ige -> Latn
+    {0x69690000u, 101u}, // ii -> Yiii
+    {0xA5280000u, 45u}, // ijj -> Latn
+    {0x696B0000u, 45u}, // ik -> Latn
+    {0xA9480000u, 45u}, // ikk -> Latn
+    {0xCD480000u, 45u}, // ikt -> Latn
+    {0xD9480000u, 45u}, // ikw -> Latn
+    {0xDD480000u, 45u}, // ikx -> Latn
+    {0xB9680000u, 45u}, // ilo -> Latn
+    {0xB9880000u, 45u}, // imo -> Latn
+    {0x696E0000u, 45u}, // in -> Latn
     {0x9DA80000u, 18u}, // inh -> Cyrl
-    {0x696F0000u, 46u}, // io -> Latn
-    {0xD1C80000u, 46u}, // iou -> Latn
-    {0xA2280000u, 46u}, // iri -> Latn
-    {0x69730000u, 46u}, // is -> Latn
-    {0x69740000u, 46u}, // it -> Latn
+    {0x696F0000u, 45u}, // io -> Latn
+    {0xD1C80000u, 45u}, // iou -> Latn
+    {0xA2280000u, 45u}, // iri -> Latn
+    {0x69730000u, 45u}, // is -> Latn
+    {0x69740000u, 45u}, // it -> Latn
     {0x69750000u, 11u}, // iu -> Cans
     {0x69770000u, 31u}, // iw -> Hebr
-    {0xB2C80000u, 46u}, // iwm -> Latn
-    {0xCAC80000u, 46u}, // iws -> Latn
-    {0x9F280000u, 46u}, // izh -> Latn
-    {0xA3280000u, 46u}, // izi -> Latn
-    {0x6A610000u, 36u}, // ja -> Jpan
-    {0x84090000u, 46u}, // jab -> Latn
-    {0xB0090000u, 46u}, // jam -> Latn
-    {0xC4090000u, 46u}, // jar -> Latn
-    {0xB8290000u, 46u}, // jbo -> Latn
-    {0xD0290000u, 46u}, // jbu -> Latn
-    {0xB4890000u, 46u}, // jen -> Latn
-    {0xA8C90000u, 46u}, // jgk -> Latn
-    {0xB8C90000u, 46u}, // jgo -> Latn
+    {0xB2C80000u, 45u}, // iwm -> Latn
+    {0xCAC80000u, 45u}, // iws -> Latn
+    {0x9F280000u, 45u}, // izh -> Latn
+    {0xA3280000u, 45u}, // izi -> Latn
+    {0x6A610000u, 35u}, // ja -> Jpan
+    {0x84090000u, 45u}, // jab -> Latn
+    {0xB0090000u, 45u}, // jam -> Latn
+    {0xC4090000u, 45u}, // jar -> Latn
+    {0xB8290000u, 45u}, // jbo -> Latn
+    {0xD0290000u, 45u}, // jbu -> Latn
+    {0xB4890000u, 45u}, // jen -> Latn
+    {0xA8C90000u, 45u}, // jgk -> Latn
+    {0xB8C90000u, 45u}, // jgo -> Latn
     {0x6A690000u, 31u}, // ji -> Hebr
-    {0x85090000u, 46u}, // jib -> Latn
-    {0x89890000u, 46u}, // jmc -> Latn
+    {0x85090000u, 45u}, // jib -> Latn
+    {0x89890000u, 45u}, // jmc -> Latn
     {0xAD890000u, 19u}, // jml -> Deva
-    {0x82290000u, 46u}, // jra -> Latn
-    {0xCE890000u, 46u}, // jut -> Latn
-    {0x6A760000u, 46u}, // jv -> Latn
-    {0x6A770000u, 46u}, // jw -> Latn
+    {0x82290000u, 45u}, // jra -> Latn
+    {0xCE890000u, 45u}, // jut -> Latn
+    {0x6A760000u, 45u}, // jv -> Latn
+    {0x6A770000u, 45u}, // jw -> Latn
     {0x6B610000u, 22u}, // ka -> Geor
     {0x800A0000u, 18u}, // kaa -> Cyrl
-    {0x840A0000u, 46u}, // kab -> Latn
-    {0x880A0000u, 46u}, // kac -> Latn
-    {0x8C0A0000u, 46u}, // kad -> Latn
-    {0xA00A0000u, 46u}, // kai -> Latn
-    {0xA40A0000u, 46u}, // kaj -> Latn
-    {0xB00A0000u, 46u}, // kam -> Latn
-    {0xB80A0000u, 46u}, // kao -> Latn
+    {0x840A0000u, 45u}, // kab -> Latn
+    {0x880A0000u, 45u}, // kac -> Latn
+    {0x8C0A0000u, 45u}, // kad -> Latn
+    {0xA00A0000u, 45u}, // kai -> Latn
+    {0xA40A0000u, 45u}, // kaj -> Latn
+    {0xB00A0000u, 45u}, // kam -> Latn
+    {0xB80A0000u, 45u}, // kao -> Latn
     {0x8C2A0000u, 18u}, // kbd -> Cyrl
-    {0xB02A0000u, 46u}, // kbm -> Latn
-    {0xBC2A0000u, 46u}, // kbp -> Latn
-    {0xC02A0000u, 46u}, // kbq -> Latn
-    {0xDC2A0000u, 46u}, // kbx -> Latn
+    {0xB02A0000u, 45u}, // kbm -> Latn
+    {0xBC2A0000u, 45u}, // kbp -> Latn
+    {0xC02A0000u, 45u}, // kbq -> Latn
+    {0xDC2A0000u, 45u}, // kbx -> Latn
     {0xE02A0000u,  2u}, // kby -> Arab
-    {0x984A0000u, 46u}, // kcg -> Latn
-    {0xA84A0000u, 46u}, // kck -> Latn
-    {0xAC4A0000u, 46u}, // kcl -> Latn
-    {0xCC4A0000u, 46u}, // kct -> Latn
-    {0x906A0000u, 46u}, // kde -> Latn
-    {0x9C6A0000u,  2u}, // kdh -> Arab
-    {0xAC6A0000u, 46u}, // kdl -> Latn
-    {0xCC6A0000u, 91u}, // kdt -> Thai
-    {0x808A0000u, 46u}, // kea -> Latn
-    {0xB48A0000u, 46u}, // ken -> Latn
-    {0xE48A0000u, 46u}, // kez -> Latn
-    {0xB8AA0000u, 46u}, // kfo -> Latn
+    {0x984A0000u, 45u}, // kcg -> Latn
+    {0xA84A0000u, 45u}, // kck -> Latn
+    {0xAC4A0000u, 45u}, // kcl -> Latn
+    {0xCC4A0000u, 45u}, // kct -> Latn
+    {0x906A0000u, 45u}, // kde -> Latn
+    {0x9C6A0000u, 45u}, // kdh -> Latn
+    {0xAC6A0000u, 45u}, // kdl -> Latn
+    {0xCC6A0000u, 92u}, // kdt -> Thai
+    {0x808A0000u, 45u}, // kea -> Latn
+    {0xB48A0000u, 45u}, // ken -> Latn
+    {0xE48A0000u, 45u}, // kez -> Latn
+    {0xB8AA0000u, 45u}, // kfo -> Latn
     {0xC4AA0000u, 19u}, // kfr -> Deva
     {0xE0AA0000u, 19u}, // kfy -> Deva
-    {0x6B670000u, 46u}, // kg -> Latn
-    {0x90CA0000u, 46u}, // kge -> Latn
-    {0x94CA0000u, 46u}, // kgf -> Latn
-    {0xBCCA0000u, 46u}, // kgp -> Latn
-    {0x80EA0000u, 46u}, // kha -> Latn
-    {0x84EA0000u, 84u}, // khb -> Talu
+    {0x6B670000u, 45u}, // kg -> Latn
+    {0x90CA0000u, 45u}, // kge -> Latn
+    {0x94CA0000u, 45u}, // kgf -> Latn
+    {0xBCCA0000u, 45u}, // kgp -> Latn
+    {0x80EA0000u, 45u}, // kha -> Latn
+    {0x84EA0000u, 85u}, // khb -> Talu
     {0xB4EA0000u, 19u}, // khn -> Deva
-    {0xC0EA0000u, 46u}, // khq -> Latn
-    {0xC8EA0000u, 46u}, // khs -> Latn
-    {0xCCEA0000u, 59u}, // kht -> Mymr
+    {0xC0EA0000u, 45u}, // khq -> Latn
+    {0xC8EA0000u, 45u}, // khs -> Latn
+    {0xCCEA0000u, 58u}, // kht -> Mymr
     {0xD8EA0000u,  2u}, // khw -> Arab
-    {0xE4EA0000u, 46u}, // khz -> Latn
-    {0x6B690000u, 46u}, // ki -> Latn
-    {0xA50A0000u, 46u}, // kij -> Latn
-    {0xD10A0000u, 46u}, // kiu -> Latn
-    {0xD90A0000u, 46u}, // kiw -> Latn
-    {0x6B6A0000u, 46u}, // kj -> Latn
-    {0x8D2A0000u, 46u}, // kjd -> Latn
-    {0x992A0000u, 45u}, // kjg -> Laoo
-    {0xC92A0000u, 46u}, // kjs -> Latn
-    {0xE12A0000u, 46u}, // kjy -> Latn
+    {0xE4EA0000u, 45u}, // khz -> Latn
+    {0x6B690000u, 45u}, // ki -> Latn
+    {0xA50A0000u, 45u}, // kij -> Latn
+    {0xD10A0000u, 45u}, // kiu -> Latn
+    {0xD90A0000u, 45u}, // kiw -> Latn
+    {0x6B6A0000u, 45u}, // kj -> Latn
+    {0x8D2A0000u, 45u}, // kjd -> Latn
+    {0x992A0000u, 44u}, // kjg -> Laoo
+    {0xC92A0000u, 45u}, // kjs -> Latn
+    {0xE12A0000u, 45u}, // kjy -> Latn
     {0x6B6B0000u, 18u}, // kk -> Cyrl
     {0x6B6B4146u,  2u}, // kk-AF -> Arab
     {0x6B6B434Eu,  2u}, // kk-CN -> Arab
     {0x6B6B4952u,  2u}, // kk-IR -> Arab
     {0x6B6B4D4Eu,  2u}, // kk-MN -> Arab
-    {0x894A0000u, 46u}, // kkc -> Latn
-    {0xA54A0000u, 46u}, // kkj -> Latn
-    {0x6B6C0000u, 46u}, // kl -> Latn
-    {0xB56A0000u, 46u}, // kln -> Latn
-    {0xC16A0000u, 46u}, // klq -> Latn
-    {0xCD6A0000u, 46u}, // klt -> Latn
-    {0xDD6A0000u, 46u}, // klx -> Latn
-    {0x6B6D0000u, 40u}, // km -> Khmr
-    {0x858A0000u, 46u}, // kmb -> Latn
-    {0x9D8A0000u, 46u}, // kmh -> Latn
-    {0xB98A0000u, 46u}, // kmo -> Latn
-    {0xC98A0000u, 46u}, // kms -> Latn
-    {0xD18A0000u, 46u}, // kmu -> Latn
-    {0xD98A0000u, 46u}, // kmw -> Latn
-    {0x6B6E0000u, 42u}, // kn -> Knda
-    {0x95AA0000u, 46u}, // knf -> Latn
-    {0xBDAA0000u, 46u}, // knp -> Latn
-    {0x6B6F0000u, 43u}, // ko -> Kore
+    {0x894A0000u, 45u}, // kkc -> Latn
+    {0xA54A0000u, 45u}, // kkj -> Latn
+    {0x6B6C0000u, 45u}, // kl -> Latn
+    {0xB56A0000u, 45u}, // kln -> Latn
+    {0xC16A0000u, 45u}, // klq -> Latn
+    {0xCD6A0000u, 45u}, // klt -> Latn
+    {0xDD6A0000u, 45u}, // klx -> Latn
+    {0x6B6D0000u, 39u}, // km -> Khmr
+    {0x858A0000u, 45u}, // kmb -> Latn
+    {0x9D8A0000u, 45u}, // kmh -> Latn
+    {0xB98A0000u, 45u}, // kmo -> Latn
+    {0xC98A0000u, 45u}, // kms -> Latn
+    {0xD18A0000u, 45u}, // kmu -> Latn
+    {0xD98A0000u, 45u}, // kmw -> Latn
+    {0x6B6E0000u, 41u}, // kn -> Knda
+    {0x95AA0000u, 45u}, // knf -> Latn
+    {0xBDAA0000u, 45u}, // knp -> Latn
+    {0x6B6F0000u, 42u}, // ko -> Kore
     {0xA1CA0000u, 18u}, // koi -> Cyrl
     {0xA9CA0000u, 19u}, // kok -> Deva
-    {0xADCA0000u, 46u}, // kol -> Latn
-    {0xC9CA0000u, 46u}, // kos -> Latn
-    {0xE5CA0000u, 46u}, // koz -> Latn
-    {0x91EA0000u, 46u}, // kpe -> Latn
-    {0x95EA0000u, 46u}, // kpf -> Latn
-    {0xB9EA0000u, 46u}, // kpo -> Latn
-    {0xC5EA0000u, 46u}, // kpr -> Latn
-    {0xDDEA0000u, 46u}, // kpx -> Latn
-    {0x860A0000u, 46u}, // kqb -> Latn
-    {0x960A0000u, 46u}, // kqf -> Latn
-    {0xCA0A0000u, 46u}, // kqs -> Latn
+    {0xADCA0000u, 45u}, // kol -> Latn
+    {0xC9CA0000u, 45u}, // kos -> Latn
+    {0xE5CA0000u, 45u}, // koz -> Latn
+    {0x91EA0000u, 45u}, // kpe -> Latn
+    {0x95EA0000u, 45u}, // kpf -> Latn
+    {0xB9EA0000u, 45u}, // kpo -> Latn
+    {0xC5EA0000u, 45u}, // kpr -> Latn
+    {0xDDEA0000u, 45u}, // kpx -> Latn
+    {0x860A0000u, 45u}, // kqb -> Latn
+    {0x960A0000u, 45u}, // kqf -> Latn
+    {0xCA0A0000u, 45u}, // kqs -> Latn
     {0xE20A0000u, 21u}, // kqy -> Ethi
-    {0x6B720000u, 46u}, // kr -> Latn
+    {0x6B720000u, 45u}, // kr -> Latn
     {0x8A2A0000u, 18u}, // krc -> Cyrl
-    {0xA22A0000u, 46u}, // kri -> Latn
-    {0xA62A0000u, 46u}, // krj -> Latn
-    {0xAE2A0000u, 46u}, // krl -> Latn
-    {0xCA2A0000u, 46u}, // krs -> Latn
+    {0xA22A0000u, 45u}, // kri -> Latn
+    {0xA62A0000u, 45u}, // krj -> Latn
+    {0xAE2A0000u, 45u}, // krl -> Latn
+    {0xCA2A0000u, 45u}, // krs -> Latn
     {0xD22A0000u, 19u}, // kru -> Deva
     {0x6B730000u,  2u}, // ks -> Arab
-    {0x864A0000u, 46u}, // ksb -> Latn
-    {0x8E4A0000u, 46u}, // ksd -> Latn
-    {0x964A0000u, 46u}, // ksf -> Latn
-    {0x9E4A0000u, 46u}, // ksh -> Latn
-    {0xA64A0000u, 46u}, // ksj -> Latn
-    {0xC64A0000u, 46u}, // ksr -> Latn
+    {0x864A0000u, 45u}, // ksb -> Latn
+    {0x8E4A0000u, 45u}, // ksd -> Latn
+    {0x964A0000u, 45u}, // ksf -> Latn
+    {0x9E4A0000u, 45u}, // ksh -> Latn
+    {0xA64A0000u, 45u}, // ksj -> Latn
+    {0xC64A0000u, 45u}, // ksr -> Latn
     {0x866A0000u, 21u}, // ktb -> Ethi
-    {0xB26A0000u, 46u}, // ktm -> Latn
-    {0xBA6A0000u, 46u}, // kto -> Latn
-    {0xC66A0000u, 46u}, // ktr -> Latn
-    {0x6B750000u, 46u}, // ku -> Latn
+    {0xB26A0000u, 45u}, // ktm -> Latn
+    {0xBA6A0000u, 45u}, // kto -> Latn
+    {0xC66A0000u, 45u}, // ktr -> Latn
+    {0x6B750000u, 45u}, // ku -> Latn
     {0x6B754952u,  2u}, // ku-IR -> Arab
     {0x6B754C42u,  2u}, // ku-LB -> Arab
-    {0x868A0000u, 46u}, // kub -> Latn
-    {0x8E8A0000u, 46u}, // kud -> Latn
-    {0x928A0000u, 46u}, // kue -> Latn
-    {0xA68A0000u, 46u}, // kuj -> Latn
+    {0x868A0000u, 45u}, // kub -> Latn
+    {0x8E8A0000u, 45u}, // kud -> Latn
+    {0x928A0000u, 45u}, // kue -> Latn
+    {0xA68A0000u, 45u}, // kuj -> Latn
     {0xB28A0000u, 18u}, // kum -> Cyrl
-    {0xB68A0000u, 46u}, // kun -> Latn
-    {0xBE8A0000u, 46u}, // kup -> Latn
-    {0xCA8A0000u, 46u}, // kus -> Latn
+    {0xB68A0000u, 45u}, // kun -> Latn
+    {0xBE8A0000u, 45u}, // kup -> Latn
+    {0xCA8A0000u, 45u}, // kus -> Latn
     {0x6B760000u, 18u}, // kv -> Cyrl
-    {0x9AAA0000u, 46u}, // kvg -> Latn
-    {0xC6AA0000u, 46u}, // kvr -> Latn
+    {0x9AAA0000u, 45u}, // kvg -> Latn
+    {0xC6AA0000u, 45u}, // kvr -> Latn
     {0xDEAA0000u,  2u}, // kvx -> Arab
-    {0x6B770000u, 46u}, // kw -> Latn
-    {0xA6CA0000u, 46u}, // kwj -> Latn
-    {0xBACA0000u, 46u}, // kwo -> Latn
-    {0xC2CA0000u, 46u}, // kwq -> Latn
-    {0x82EA0000u, 46u}, // kxa -> Latn
+    {0x6B770000u, 45u}, // kw -> Latn
+    {0xA6CA0000u, 45u}, // kwj -> Latn
+    {0xBACA0000u, 45u}, // kwo -> Latn
+    {0xC2CA0000u, 45u}, // kwq -> Latn
+    {0x82EA0000u, 45u}, // kxa -> Latn
     {0x8AEA0000u, 21u}, // kxc -> Ethi
-    {0x92EA0000u, 46u}, // kxe -> Latn
+    {0x92EA0000u, 45u}, // kxe -> Latn
     {0xAEEA0000u, 19u}, // kxl -> Deva
-    {0xB2EA0000u, 91u}, // kxm -> Thai
+    {0xB2EA0000u, 92u}, // kxm -> Thai
     {0xBEEA0000u,  2u}, // kxp -> Arab
-    {0xDAEA0000u, 46u}, // kxw -> Latn
-    {0xE6EA0000u, 46u}, // kxz -> Latn
+    {0xDAEA0000u, 45u}, // kxw -> Latn
+    {0xE6EA0000u, 45u}, // kxz -> Latn
     {0x6B790000u, 18u}, // ky -> Cyrl
     {0x6B79434Eu,  2u}, // ky-CN -> Arab
-    {0x6B795452u, 46u}, // ky-TR -> Latn
-    {0x930A0000u, 46u}, // kye -> Latn
-    {0xDF0A0000u, 46u}, // kyx -> Latn
+    {0x6B795452u, 45u}, // ky-TR -> Latn
+    {0x930A0000u, 45u}, // kye -> Latn
+    {0xDF0A0000u, 45u}, // kyx -> Latn
     {0x9F2A0000u,  2u}, // kzh -> Arab
-    {0xA72A0000u, 46u}, // kzj -> Latn
-    {0xC72A0000u, 46u}, // kzr -> Latn
-    {0xCF2A0000u, 46u}, // kzt -> Latn
-    {0x6C610000u, 46u}, // la -> Latn
-    {0x840B0000u, 48u}, // lab -> Lina
+    {0xA72A0000u, 45u}, // kzj -> Latn
+    {0xC72A0000u, 45u}, // kzr -> Latn
+    {0xCF2A0000u, 45u}, // kzt -> Latn
+    {0x6C610000u, 45u}, // la -> Latn
+    {0x840B0000u, 47u}, // lab -> Lina
     {0x8C0B0000u, 31u}, // lad -> Hebr
-    {0x980B0000u, 46u}, // lag -> Latn
+    {0x980B0000u, 45u}, // lag -> Latn
     {0x9C0B0000u,  2u}, // lah -> Arab
-    {0xA40B0000u, 46u}, // laj -> Latn
-    {0xC80B0000u, 46u}, // las -> Latn
-    {0x6C620000u, 46u}, // lb -> Latn
+    {0xA40B0000u, 45u}, // laj -> Latn
+    {0xC80B0000u, 45u}, // las -> Latn
+    {0x6C620000u, 45u}, // lb -> Latn
     {0x902B0000u, 18u}, // lbe -> Cyrl
-    {0xD02B0000u, 46u}, // lbu -> Latn
-    {0xD82B0000u, 46u}, // lbw -> Latn
-    {0xB04B0000u, 46u}, // lcm -> Latn
-    {0xBC4B0000u, 91u}, // lcp -> Thai
-    {0x846B0000u, 46u}, // ldb -> Latn
-    {0x8C8B0000u, 46u}, // led -> Latn
-    {0x908B0000u, 46u}, // lee -> Latn
-    {0xB08B0000u, 46u}, // lem -> Latn
-    {0xBC8B0000u, 47u}, // lep -> Lepc
-    {0xC08B0000u, 46u}, // leq -> Latn
-    {0xD08B0000u, 46u}, // leu -> Latn
+    {0xD02B0000u, 45u}, // lbu -> Latn
+    {0xD82B0000u, 45u}, // lbw -> Latn
+    {0xB04B0000u, 45u}, // lcm -> Latn
+    {0xBC4B0000u, 92u}, // lcp -> Thai
+    {0x846B0000u, 45u}, // ldb -> Latn
+    {0x8C8B0000u, 45u}, // led -> Latn
+    {0x908B0000u, 45u}, // lee -> Latn
+    {0xB08B0000u, 45u}, // lem -> Latn
+    {0xBC8B0000u, 46u}, // lep -> Lepc
+    {0xC08B0000u, 45u}, // leq -> Latn
+    {0xD08B0000u, 45u}, // leu -> Latn
     {0xE48B0000u, 18u}, // lez -> Cyrl
-    {0x6C670000u, 46u}, // lg -> Latn
-    {0x98CB0000u, 46u}, // lgg -> Latn
-    {0x6C690000u, 46u}, // li -> Latn
-    {0x810B0000u, 46u}, // lia -> Latn
-    {0x8D0B0000u, 46u}, // lid -> Latn
+    {0x6C670000u, 45u}, // lg -> Latn
+    {0x98CB0000u, 45u}, // lgg -> Latn
+    {0x6C690000u, 45u}, // li -> Latn
+    {0x810B0000u, 45u}, // lia -> Latn
+    {0x8D0B0000u, 45u}, // lid -> Latn
     {0x950B0000u, 19u}, // lif -> Deva
-    {0x990B0000u, 46u}, // lig -> Latn
-    {0x9D0B0000u, 46u}, // lih -> Latn
-    {0xA50B0000u, 46u}, // lij -> Latn
-    {0xC90B0000u, 49u}, // lis -> Lisu
-    {0xBD2B0000u, 46u}, // ljp -> Latn
+    {0x990B0000u, 45u}, // lig -> Latn
+    {0x9D0B0000u, 45u}, // lih -> Latn
+    {0xA50B0000u, 45u}, // lij -> Latn
+    {0xC90B0000u, 48u}, // lis -> Lisu
+    {0xBD2B0000u, 45u}, // ljp -> Latn
     {0xA14B0000u,  2u}, // lki -> Arab
-    {0xCD4B0000u, 46u}, // lkt -> Latn
-    {0x916B0000u, 46u}, // lle -> Latn
-    {0xB56B0000u, 46u}, // lln -> Latn
-    {0xB58B0000u, 88u}, // lmn -> Telu
-    {0xB98B0000u, 46u}, // lmo -> Latn
-    {0xBD8B0000u, 46u}, // lmp -> Latn
-    {0x6C6E0000u, 46u}, // ln -> Latn
-    {0xC9AB0000u, 46u}, // lns -> Latn
-    {0xD1AB0000u, 46u}, // lnu -> Latn
-    {0x6C6F0000u, 45u}, // lo -> Laoo
-    {0xA5CB0000u, 46u}, // loj -> Latn
-    {0xA9CB0000u, 46u}, // lok -> Latn
-    {0xADCB0000u, 46u}, // lol -> Latn
-    {0xC5CB0000u, 46u}, // lor -> Latn
-    {0xC9CB0000u, 46u}, // los -> Latn
-    {0xE5CB0000u, 46u}, // loz -> Latn
+    {0xCD4B0000u, 45u}, // lkt -> Latn
+    {0x916B0000u, 45u}, // lle -> Latn
+    {0xB56B0000u, 45u}, // lln -> Latn
+    {0xB58B0000u, 89u}, // lmn -> Telu
+    {0xB98B0000u, 45u}, // lmo -> Latn
+    {0xBD8B0000u, 45u}, // lmp -> Latn
+    {0x6C6E0000u, 45u}, // ln -> Latn
+    {0xC9AB0000u, 45u}, // lns -> Latn
+    {0xD1AB0000u, 45u}, // lnu -> Latn
+    {0x6C6F0000u, 44u}, // lo -> Laoo
+    {0xA5CB0000u, 45u}, // loj -> Latn
+    {0xA9CB0000u, 45u}, // lok -> Latn
+    {0xADCB0000u, 45u}, // lol -> Latn
+    {0xC5CB0000u, 45u}, // lor -> Latn
+    {0xC9CB0000u, 45u}, // los -> Latn
+    {0xE5CB0000u, 45u}, // loz -> Latn
     {0x8A2B0000u,  2u}, // lrc -> Arab
-    {0x6C740000u, 46u}, // lt -> Latn
-    {0x9A6B0000u, 46u}, // ltg -> Latn
-    {0x6C750000u, 46u}, // lu -> Latn
-    {0x828B0000u, 46u}, // lua -> Latn
-    {0xBA8B0000u, 46u}, // luo -> Latn
-    {0xE28B0000u, 46u}, // luy -> Latn
+    {0x6C740000u, 45u}, // lt -> Latn
+    {0x9A6B0000u, 45u}, // ltg -> Latn
+    {0x6C750000u, 45u}, // lu -> Latn
+    {0x828B0000u, 45u}, // lua -> Latn
+    {0xBA8B0000u, 45u}, // luo -> Latn
+    {0xE28B0000u, 45u}, // luy -> Latn
     {0xE68B0000u,  2u}, // luz -> Arab
-    {0x6C760000u, 46u}, // lv -> Latn
-    {0xAECB0000u, 91u}, // lwl -> Thai
+    {0x6C760000u, 45u}, // lv -> Latn
+    {0xAECB0000u, 92u}, // lwl -> Thai
     {0x9F2B0000u, 29u}, // lzh -> Hans
-    {0xE72B0000u, 46u}, // lzz -> Latn
-    {0x8C0C0000u, 46u}, // mad -> Latn
-    {0x940C0000u, 46u}, // maf -> Latn
+    {0xE72B0000u, 45u}, // lzz -> Latn
+    {0x8C0C0000u, 45u}, // mad -> Latn
+    {0x940C0000u, 45u}, // maf -> Latn
     {0x980C0000u, 19u}, // mag -> Deva
     {0xA00C0000u, 19u}, // mai -> Deva
-    {0xA80C0000u, 46u}, // mak -> Latn
-    {0xB40C0000u, 46u}, // man -> Latn
-    {0xB40C474Eu, 61u}, // man-GN -> Nkoo
-    {0xC80C0000u, 46u}, // mas -> Latn
-    {0xD80C0000u, 46u}, // maw -> Latn
-    {0xE40C0000u, 46u}, // maz -> Latn
-    {0x9C2C0000u, 46u}, // mbh -> Latn
-    {0xB82C0000u, 46u}, // mbo -> Latn
-    {0xC02C0000u, 46u}, // mbq -> Latn
-    {0xD02C0000u, 46u}, // mbu -> Latn
-    {0xD82C0000u, 46u}, // mbw -> Latn
-    {0xA04C0000u, 46u}, // mci -> Latn
-    {0xBC4C0000u, 46u}, // mcp -> Latn
-    {0xC04C0000u, 46u}, // mcq -> Latn
-    {0xC44C0000u, 46u}, // mcr -> Latn
-    {0xD04C0000u, 46u}, // mcu -> Latn
-    {0x806C0000u, 46u}, // mda -> Latn
+    {0xA80C0000u, 45u}, // mak -> Latn
+    {0xB40C0000u, 45u}, // man -> Latn
+    {0xB40C474Eu, 60u}, // man-GN -> Nkoo
+    {0xC80C0000u, 45u}, // mas -> Latn
+    {0xD80C0000u, 45u}, // maw -> Latn
+    {0xE40C0000u, 45u}, // maz -> Latn
+    {0x9C2C0000u, 45u}, // mbh -> Latn
+    {0xB82C0000u, 45u}, // mbo -> Latn
+    {0xC02C0000u, 45u}, // mbq -> Latn
+    {0xD02C0000u, 45u}, // mbu -> Latn
+    {0xD82C0000u, 45u}, // mbw -> Latn
+    {0xA04C0000u, 45u}, // mci -> Latn
+    {0xBC4C0000u, 45u}, // mcp -> Latn
+    {0xC04C0000u, 45u}, // mcq -> Latn
+    {0xC44C0000u, 45u}, // mcr -> Latn
+    {0xD04C0000u, 45u}, // mcu -> Latn
+    {0x806C0000u, 45u}, // mda -> Latn
     {0x906C0000u,  2u}, // mde -> Arab
     {0x946C0000u, 18u}, // mdf -> Cyrl
-    {0x9C6C0000u, 46u}, // mdh -> Latn
-    {0xA46C0000u, 46u}, // mdj -> Latn
-    {0xC46C0000u, 46u}, // mdr -> Latn
+    {0x9C6C0000u, 45u}, // mdh -> Latn
+    {0xA46C0000u, 45u}, // mdj -> Latn
+    {0xC46C0000u, 45u}, // mdr -> Latn
     {0xDC6C0000u, 21u}, // mdx -> Ethi
-    {0x8C8C0000u, 46u}, // med -> Latn
-    {0x908C0000u, 46u}, // mee -> Latn
-    {0xA88C0000u, 46u}, // mek -> Latn
-    {0xB48C0000u, 46u}, // men -> Latn
-    {0xC48C0000u, 46u}, // mer -> Latn
-    {0xCC8C0000u, 46u}, // met -> Latn
-    {0xD08C0000u, 46u}, // meu -> Latn
+    {0x8C8C0000u, 45u}, // med -> Latn
+    {0x908C0000u, 45u}, // mee -> Latn
+    {0xA88C0000u, 45u}, // mek -> Latn
+    {0xB48C0000u, 45u}, // men -> Latn
+    {0xC48C0000u, 45u}, // mer -> Latn
+    {0xCC8C0000u, 45u}, // met -> Latn
+    {0xD08C0000u, 45u}, // meu -> Latn
     {0x80AC0000u,  2u}, // mfa -> Arab
-    {0x90AC0000u, 46u}, // mfe -> Latn
-    {0xB4AC0000u, 46u}, // mfn -> Latn
-    {0xB8AC0000u, 46u}, // mfo -> Latn
-    {0xC0AC0000u, 46u}, // mfq -> Latn
-    {0x6D670000u, 46u}, // mg -> Latn
-    {0x9CCC0000u, 46u}, // mgh -> Latn
-    {0xACCC0000u, 46u}, // mgl -> Latn
-    {0xB8CC0000u, 46u}, // mgo -> Latn
+    {0x90AC0000u, 45u}, // mfe -> Latn
+    {0xB4AC0000u, 45u}, // mfn -> Latn
+    {0xB8AC0000u, 45u}, // mfo -> Latn
+    {0xC0AC0000u, 45u}, // mfq -> Latn
+    {0x6D670000u, 45u}, // mg -> Latn
+    {0x9CCC0000u, 45u}, // mgh -> Latn
+    {0xACCC0000u, 45u}, // mgl -> Latn
+    {0xB8CC0000u, 45u}, // mgo -> Latn
     {0xBCCC0000u, 19u}, // mgp -> Deva
-    {0xE0CC0000u, 46u}, // mgy -> Latn
-    {0x6D680000u, 46u}, // mh -> Latn
-    {0xA0EC0000u, 46u}, // mhi -> Latn
-    {0xACEC0000u, 46u}, // mhl -> Latn
-    {0x6D690000u, 46u}, // mi -> Latn
-    {0x950C0000u, 46u}, // mif -> Latn
-    {0xB50C0000u, 46u}, // min -> Latn
-    {0xD90C0000u, 46u}, // miw -> Latn
+    {0xE0CC0000u, 45u}, // mgy -> Latn
+    {0x6D680000u, 45u}, // mh -> Latn
+    {0xA0EC0000u, 45u}, // mhi -> Latn
+    {0xACEC0000u, 45u}, // mhl -> Latn
+    {0x6D690000u, 45u}, // mi -> Latn
+    {0x950C0000u, 45u}, // mif -> Latn
+    {0xB50C0000u, 45u}, // min -> Latn
+    {0xD90C0000u, 45u}, // miw -> Latn
     {0x6D6B0000u, 18u}, // mk -> Cyrl
     {0xA14C0000u,  2u}, // mki -> Arab
-    {0xAD4C0000u, 46u}, // mkl -> Latn
-    {0xBD4C0000u, 46u}, // mkp -> Latn
-    {0xD94C0000u, 46u}, // mkw -> Latn
-    {0x6D6C0000u, 56u}, // ml -> Mlym
-    {0x916C0000u, 46u}, // mle -> Latn
-    {0xBD6C0000u, 46u}, // mlp -> Latn
-    {0xC96C0000u, 46u}, // mls -> Latn
-    {0xB98C0000u, 46u}, // mmo -> Latn
-    {0xD18C0000u, 46u}, // mmu -> Latn
-    {0xDD8C0000u, 46u}, // mmx -> Latn
+    {0xAD4C0000u, 45u}, // mkl -> Latn
+    {0xBD4C0000u, 45u}, // mkp -> Latn
+    {0xD94C0000u, 45u}, // mkw -> Latn
+    {0x6D6C0000u, 55u}, // ml -> Mlym
+    {0x916C0000u, 45u}, // mle -> Latn
+    {0xBD6C0000u, 45u}, // mlp -> Latn
+    {0xC96C0000u, 45u}, // mls -> Latn
+    {0xB98C0000u, 45u}, // mmo -> Latn
+    {0xD18C0000u, 45u}, // mmu -> Latn
+    {0xDD8C0000u, 45u}, // mmx -> Latn
     {0x6D6E0000u, 18u}, // mn -> Cyrl
-    {0x6D6E434Eu, 57u}, // mn-CN -> Mong
-    {0x81AC0000u, 46u}, // mna -> Latn
-    {0x95AC0000u, 46u}, // mnf -> Latn
+    {0x6D6E434Eu, 56u}, // mn-CN -> Mong
+    {0x81AC0000u, 45u}, // mna -> Latn
+    {0x95AC0000u, 45u}, // mnf -> Latn
     {0xA1AC0000u,  8u}, // mni -> Beng
-    {0xD9AC0000u, 59u}, // mnw -> Mymr
-    {0x6D6F0000u, 46u}, // mo -> Latn
-    {0x81CC0000u, 46u}, // moa -> Latn
-    {0x91CC0000u, 46u}, // moe -> Latn
-    {0x9DCC0000u, 46u}, // moh -> Latn
-    {0xC9CC0000u, 46u}, // mos -> Latn
-    {0xDDCC0000u, 46u}, // mox -> Latn
-    {0xBDEC0000u, 46u}, // mpp -> Latn
-    {0xC9EC0000u, 46u}, // mps -> Latn
-    {0xCDEC0000u, 46u}, // mpt -> Latn
-    {0xDDEC0000u, 46u}, // mpx -> Latn
-    {0xAE0C0000u, 46u}, // mql -> Latn
+    {0xD9AC0000u, 58u}, // mnw -> Mymr
+    {0x6D6F0000u, 45u}, // mo -> Latn
+    {0x81CC0000u, 45u}, // moa -> Latn
+    {0x91CC0000u, 45u}, // moe -> Latn
+    {0x9DCC0000u, 45u}, // moh -> Latn
+    {0xC9CC0000u, 45u}, // mos -> Latn
+    {0xDDCC0000u, 45u}, // mox -> Latn
+    {0xBDEC0000u, 45u}, // mpp -> Latn
+    {0xC9EC0000u, 45u}, // mps -> Latn
+    {0xCDEC0000u, 45u}, // mpt -> Latn
+    {0xDDEC0000u, 45u}, // mpx -> Latn
+    {0xAE0C0000u, 45u}, // mql -> Latn
     {0x6D720000u, 19u}, // mr -> Deva
     {0x8E2C0000u, 19u}, // mrd -> Deva
     {0xA62C0000u, 18u}, // mrj -> Cyrl
-    {0xBA2C0000u, 58u}, // mro -> Mroo
-    {0x6D730000u, 46u}, // ms -> Latn
+    {0xBA2C0000u, 57u}, // mro -> Mroo
+    {0x6D730000u, 45u}, // ms -> Latn
     {0x6D734343u,  2u}, // ms-CC -> Arab
-    {0x6D740000u, 46u}, // mt -> Latn
-    {0x8A6C0000u, 46u}, // mtc -> Latn
-    {0x966C0000u, 46u}, // mtf -> Latn
-    {0xA26C0000u, 46u}, // mti -> Latn
+    {0x6D740000u, 45u}, // mt -> Latn
+    {0x8A6C0000u, 45u}, // mtc -> Latn
+    {0x966C0000u, 45u}, // mtf -> Latn
+    {0xA26C0000u, 45u}, // mti -> Latn
     {0xC66C0000u, 19u}, // mtr -> Deva
-    {0x828C0000u, 46u}, // mua -> Latn
-    {0xC68C0000u, 46u}, // mur -> Latn
-    {0xCA8C0000u, 46u}, // mus -> Latn
-    {0x82AC0000u, 46u}, // mva -> Latn
-    {0xB6AC0000u, 46u}, // mvn -> Latn
+    {0x828C0000u, 45u}, // mua -> Latn
+    {0xC68C0000u, 45u}, // mur -> Latn
+    {0xCA8C0000u, 45u}, // mus -> Latn
+    {0x82AC0000u, 45u}, // mva -> Latn
+    {0xB6AC0000u, 45u}, // mvn -> Latn
     {0xE2AC0000u,  2u}, // mvy -> Arab
-    {0xAACC0000u, 46u}, // mwk -> Latn
+    {0xAACC0000u, 45u}, // mwk -> Latn
     {0xC6CC0000u, 19u}, // mwr -> Deva
-    {0xD6CC0000u, 46u}, // mwv -> Latn
-    {0xDACC0000u, 34u}, // mww -> Hmnp
-    {0x8AEC0000u, 46u}, // mxc -> Latn
-    {0xB2EC0000u, 46u}, // mxm -> Latn
-    {0x6D790000u, 59u}, // my -> Mymr
-    {0xAB0C0000u, 46u}, // myk -> Latn
+    {0xD6CC0000u, 45u}, // mwv -> Latn
+    {0xDACC0000u, 33u}, // mww -> Hmnp
+    {0x8AEC0000u, 45u}, // mxc -> Latn
+    {0xB2EC0000u, 45u}, // mxm -> Latn
+    {0x6D790000u, 58u}, // my -> Mymr
+    {0xAB0C0000u, 45u}, // myk -> Latn
     {0xB30C0000u, 21u}, // mym -> Ethi
     {0xD70C0000u, 18u}, // myv -> Cyrl
-    {0xDB0C0000u, 46u}, // myw -> Latn
-    {0xDF0C0000u, 46u}, // myx -> Latn
-    {0xE70C0000u, 52u}, // myz -> Mand
-    {0xAB2C0000u, 46u}, // mzk -> Latn
-    {0xB32C0000u, 46u}, // mzm -> Latn
+    {0xDB0C0000u, 45u}, // myw -> Latn
+    {0xDF0C0000u, 45u}, // myx -> Latn
+    {0xE70C0000u, 51u}, // myz -> Mand
+    {0xAB2C0000u, 45u}, // mzk -> Latn
+    {0xB32C0000u, 45u}, // mzm -> Latn
     {0xB72C0000u,  2u}, // mzn -> Arab
-    {0xBF2C0000u, 46u}, // mzp -> Latn
-    {0xDB2C0000u, 46u}, // mzw -> Latn
-    {0xE72C0000u, 46u}, // mzz -> Latn
-    {0x6E610000u, 46u}, // na -> Latn
-    {0x880D0000u, 46u}, // nac -> Latn
-    {0x940D0000u, 46u}, // naf -> Latn
-    {0xA80D0000u, 46u}, // nak -> Latn
+    {0xBF2C0000u, 45u}, // mzp -> Latn
+    {0xDB2C0000u, 45u}, // mzw -> Latn
+    {0xE72C0000u, 45u}, // mzz -> Latn
+    {0x6E610000u, 45u}, // na -> Latn
+    {0x880D0000u, 45u}, // nac -> Latn
+    {0x940D0000u, 45u}, // naf -> Latn
+    {0xA80D0000u, 45u}, // nak -> Latn
     {0xB40D0000u, 29u}, // nan -> Hans
-    {0xBC0D0000u, 46u}, // nap -> Latn
-    {0xC00D0000u, 46u}, // naq -> Latn
-    {0xC80D0000u, 46u}, // nas -> Latn
-    {0x6E620000u, 46u}, // nb -> Latn
-    {0x804D0000u, 46u}, // nca -> Latn
-    {0x904D0000u, 46u}, // nce -> Latn
-    {0x944D0000u, 46u}, // ncf -> Latn
-    {0x9C4D0000u, 46u}, // nch -> Latn
-    {0xB84D0000u, 46u}, // nco -> Latn
-    {0xD04D0000u, 46u}, // ncu -> Latn
-    {0x6E640000u, 46u}, // nd -> Latn
-    {0x886D0000u, 46u}, // ndc -> Latn
-    {0xC86D0000u, 46u}, // nds -> Latn
+    {0xBC0D0000u, 45u}, // nap -> Latn
+    {0xC00D0000u, 45u}, // naq -> Latn
+    {0xC80D0000u, 45u}, // nas -> Latn
+    {0x6E620000u, 45u}, // nb -> Latn
+    {0x804D0000u, 45u}, // nca -> Latn
+    {0x904D0000u, 45u}, // nce -> Latn
+    {0x944D0000u, 45u}, // ncf -> Latn
+    {0x9C4D0000u, 45u}, // nch -> Latn
+    {0xB84D0000u, 45u}, // nco -> Latn
+    {0xD04D0000u, 45u}, // ncu -> Latn
+    {0x6E640000u, 45u}, // nd -> Latn
+    {0x886D0000u, 45u}, // ndc -> Latn
+    {0xC86D0000u, 45u}, // nds -> Latn
     {0x6E650000u, 19u}, // ne -> Deva
-    {0x848D0000u, 46u}, // neb -> Latn
+    {0x848D0000u, 45u}, // neb -> Latn
     {0xD88D0000u, 19u}, // new -> Deva
-    {0xDC8D0000u, 46u}, // nex -> Latn
-    {0xC4AD0000u, 46u}, // nfr -> Latn
-    {0x6E670000u, 46u}, // ng -> Latn
-    {0x80CD0000u, 46u}, // nga -> Latn
-    {0x84CD0000u, 46u}, // ngb -> Latn
-    {0xACCD0000u, 46u}, // ngl -> Latn
-    {0x84ED0000u, 46u}, // nhb -> Latn
-    {0x90ED0000u, 46u}, // nhe -> Latn
-    {0xD8ED0000u, 46u}, // nhw -> Latn
-    {0x950D0000u, 46u}, // nif -> Latn
-    {0xA10D0000u, 46u}, // nii -> Latn
-    {0xA50D0000u, 46u}, // nij -> Latn
-    {0xB50D0000u, 46u}, // nin -> Latn
-    {0xD10D0000u, 46u}, // niu -> Latn
-    {0xE10D0000u, 46u}, // niy -> Latn
-    {0xE50D0000u, 46u}, // niz -> Latn
-    {0xB92D0000u, 46u}, // njo -> Latn
-    {0x994D0000u, 46u}, // nkg -> Latn
-    {0xB94D0000u, 46u}, // nko -> Latn
-    {0x6E6C0000u, 46u}, // nl -> Latn
-    {0x998D0000u, 46u}, // nmg -> Latn
-    {0xE58D0000u, 46u}, // nmz -> Latn
-    {0x6E6E0000u, 46u}, // nn -> Latn
-    {0x95AD0000u, 46u}, // nnf -> Latn
-    {0x9DAD0000u, 46u}, // nnh -> Latn
-    {0xA9AD0000u, 46u}, // nnk -> Latn
-    {0xB1AD0000u, 46u}, // nnm -> Latn
-    {0xBDAD0000u, 95u}, // nnp -> Wcho
-    {0x6E6F0000u, 46u}, // no -> Latn
-    {0x8DCD0000u, 44u}, // nod -> Lana
+    {0xDC8D0000u, 45u}, // nex -> Latn
+    {0xC4AD0000u, 45u}, // nfr -> Latn
+    {0x6E670000u, 45u}, // ng -> Latn
+    {0x80CD0000u, 45u}, // nga -> Latn
+    {0x84CD0000u, 45u}, // ngb -> Latn
+    {0xACCD0000u, 45u}, // ngl -> Latn
+    {0x84ED0000u, 45u}, // nhb -> Latn
+    {0x90ED0000u, 45u}, // nhe -> Latn
+    {0xD8ED0000u, 45u}, // nhw -> Latn
+    {0x950D0000u, 45u}, // nif -> Latn
+    {0xA10D0000u, 45u}, // nii -> Latn
+    {0xA50D0000u, 45u}, // nij -> Latn
+    {0xB50D0000u, 45u}, // nin -> Latn
+    {0xD10D0000u, 45u}, // niu -> Latn
+    {0xE10D0000u, 45u}, // niy -> Latn
+    {0xE50D0000u, 45u}, // niz -> Latn
+    {0xB92D0000u, 45u}, // njo -> Latn
+    {0x994D0000u, 45u}, // nkg -> Latn
+    {0xB94D0000u, 45u}, // nko -> Latn
+    {0x6E6C0000u, 45u}, // nl -> Latn
+    {0x998D0000u, 45u}, // nmg -> Latn
+    {0xE58D0000u, 45u}, // nmz -> Latn
+    {0x6E6E0000u, 45u}, // nn -> Latn
+    {0x95AD0000u, 45u}, // nnf -> Latn
+    {0x9DAD0000u, 45u}, // nnh -> Latn
+    {0xA9AD0000u, 45u}, // nnk -> Latn
+    {0xB1AD0000u, 45u}, // nnm -> Latn
+    {0xBDAD0000u, 98u}, // nnp -> Wcho
+    {0x6E6F0000u, 45u}, // no -> Latn
+    {0x8DCD0000u, 43u}, // nod -> Lana
     {0x91CD0000u, 19u}, // noe -> Deva
-    {0xB5CD0000u, 73u}, // non -> Runr
-    {0xBDCD0000u, 46u}, // nop -> Latn
-    {0xD1CD0000u, 46u}, // nou -> Latn
-    {0xBA0D0000u, 61u}, // nqo -> Nkoo
-    {0x6E720000u, 46u}, // nr -> Latn
-    {0x862D0000u, 46u}, // nrb -> Latn
+    {0xB5CD0000u, 74u}, // non -> Runr
+    {0xBDCD0000u, 45u}, // nop -> Latn
+    {0xD1CD0000u, 45u}, // nou -> Latn
+    {0xBA0D0000u, 60u}, // nqo -> Nkoo
+    {0x6E720000u, 45u}, // nr -> Latn
+    {0x862D0000u, 45u}, // nrb -> Latn
     {0xAA4D0000u, 11u}, // nsk -> Cans
-    {0xB64D0000u, 46u}, // nsn -> Latn
-    {0xBA4D0000u, 46u}, // nso -> Latn
-    {0xCA4D0000u, 46u}, // nss -> Latn
-    {0xB26D0000u, 46u}, // ntm -> Latn
-    {0xC66D0000u, 46u}, // ntr -> Latn
-    {0xA28D0000u, 46u}, // nui -> Latn
-    {0xBE8D0000u, 46u}, // nup -> Latn
-    {0xCA8D0000u, 46u}, // nus -> Latn
-    {0xD68D0000u, 46u}, // nuv -> Latn
-    {0xDE8D0000u, 46u}, // nux -> Latn
-    {0x6E760000u, 46u}, // nv -> Latn
-    {0x86CD0000u, 46u}, // nwb -> Latn
-    {0xC2ED0000u, 46u}, // nxq -> Latn
-    {0xC6ED0000u, 46u}, // nxr -> Latn
-    {0x6E790000u, 46u}, // ny -> Latn
-    {0xB30D0000u, 46u}, // nym -> Latn
-    {0xB70D0000u, 46u}, // nyn -> Latn
-    {0xA32D0000u, 46u}, // nzi -> Latn
-    {0x6F630000u, 46u}, // oc -> Latn
-    {0x88CE0000u, 46u}, // ogc -> Latn
-    {0xC54E0000u, 46u}, // okr -> Latn
-    {0xD54E0000u, 46u}, // okv -> Latn
-    {0x6F6D0000u, 46u}, // om -> Latn
-    {0x99AE0000u, 46u}, // ong -> Latn
-    {0xB5AE0000u, 46u}, // onn -> Latn
-    {0xC9AE0000u, 46u}, // ons -> Latn
-    {0xB1EE0000u, 46u}, // opm -> Latn
-    {0x6F720000u, 66u}, // or -> Orya
-    {0xBA2E0000u, 46u}, // oro -> Latn
+    {0xB64D0000u, 45u}, // nsn -> Latn
+    {0xBA4D0000u, 45u}, // nso -> Latn
+    {0xCA4D0000u, 45u}, // nss -> Latn
+    {0xCE4D0000u, 94u}, // nst -> Tnsa
+    {0xB26D0000u, 45u}, // ntm -> Latn
+    {0xC66D0000u, 45u}, // ntr -> Latn
+    {0xA28D0000u, 45u}, // nui -> Latn
+    {0xBE8D0000u, 45u}, // nup -> Latn
+    {0xCA8D0000u, 45u}, // nus -> Latn
+    {0xD68D0000u, 45u}, // nuv -> Latn
+    {0xDE8D0000u, 45u}, // nux -> Latn
+    {0x6E760000u, 45u}, // nv -> Latn
+    {0x86CD0000u, 45u}, // nwb -> Latn
+    {0xC2ED0000u, 45u}, // nxq -> Latn
+    {0xC6ED0000u, 45u}, // nxr -> Latn
+    {0x6E790000u, 45u}, // ny -> Latn
+    {0xB30D0000u, 45u}, // nym -> Latn
+    {0xB70D0000u, 45u}, // nyn -> Latn
+    {0xA32D0000u, 45u}, // nzi -> Latn
+    {0x6F630000u, 45u}, // oc -> Latn
+    {0x88CE0000u, 45u}, // ogc -> Latn
+    {0xC54E0000u, 45u}, // okr -> Latn
+    {0xD54E0000u, 45u}, // okv -> Latn
+    {0x6F6D0000u, 45u}, // om -> Latn
+    {0x99AE0000u, 45u}, // ong -> Latn
+    {0xB5AE0000u, 45u}, // onn -> Latn
+    {0xC9AE0000u, 45u}, // ons -> Latn
+    {0xB1EE0000u, 45u}, // opm -> Latn
+    {0x6F720000u, 65u}, // or -> Orya
+    {0xBA2E0000u, 45u}, // oro -> Latn
     {0xD22E0000u,  2u}, // oru -> Arab
     {0x6F730000u, 18u}, // os -> Cyrl
-    {0x824E0000u, 67u}, // osa -> Osge
+    {0x824E0000u, 66u}, // osa -> Osge
     {0x826E0000u,  2u}, // ota -> Arab
-    {0xAA6E0000u, 65u}, // otk -> Orkh
-    {0xB32E0000u, 46u}, // ozm -> Latn
+    {0xAA6E0000u, 64u}, // otk -> Orkh
+    {0xA28E0000u, 67u}, // oui -> Ougr
+    {0xB32E0000u, 45u}, // ozm -> Latn
     {0x70610000u, 28u}, // pa -> Guru
     {0x7061504Bu,  2u}, // pa-PK -> Arab
-    {0x980F0000u, 46u}, // pag -> Latn
+    {0x980F0000u, 45u}, // pag -> Latn
     {0xAC0F0000u, 69u}, // pal -> Phli
-    {0xB00F0000u, 46u}, // pam -> Latn
-    {0xBC0F0000u, 46u}, // pap -> Latn
-    {0xD00F0000u, 46u}, // pau -> Latn
-    {0xA02F0000u, 46u}, // pbi -> Latn
-    {0x8C4F0000u, 46u}, // pcd -> Latn
-    {0xB04F0000u, 46u}, // pcm -> Latn
-    {0x886F0000u, 46u}, // pdc -> Latn
-    {0xCC6F0000u, 46u}, // pdt -> Latn
-    {0x8C8F0000u, 46u}, // ped -> Latn
-    {0xB88F0000u, 96u}, // peo -> Xpeo
-    {0xDC8F0000u, 46u}, // pex -> Latn
-    {0xACAF0000u, 46u}, // pfl -> Latn
+    {0xB00F0000u, 45u}, // pam -> Latn
+    {0xBC0F0000u, 45u}, // pap -> Latn
+    {0xD00F0000u, 45u}, // pau -> Latn
+    {0xA02F0000u, 45u}, // pbi -> Latn
+    {0x8C4F0000u, 45u}, // pcd -> Latn
+    {0xB04F0000u, 45u}, // pcm -> Latn
+    {0x886F0000u, 45u}, // pdc -> Latn
+    {0xCC6F0000u, 45u}, // pdt -> Latn
+    {0x8C8F0000u, 45u}, // ped -> Latn
+    {0xB88F0000u, 99u}, // peo -> Xpeo
+    {0xDC8F0000u, 45u}, // pex -> Latn
+    {0xACAF0000u, 45u}, // pfl -> Latn
     {0xACEF0000u,  2u}, // phl -> Arab
     {0xB4EF0000u, 70u}, // phn -> Phnx
-    {0xAD0F0000u, 46u}, // pil -> Latn
-    {0xBD0F0000u, 46u}, // pip -> Latn
+    {0xAD0F0000u, 45u}, // pil -> Latn
+    {0xBD0F0000u, 45u}, // pip -> Latn
     {0x814F0000u,  9u}, // pka -> Brah
-    {0xB94F0000u, 46u}, // pko -> Latn
-    {0x706C0000u, 46u}, // pl -> Latn
-    {0x816F0000u, 46u}, // pla -> Latn
-    {0xC98F0000u, 46u}, // pms -> Latn
-    {0x99AF0000u, 46u}, // png -> Latn
-    {0xB5AF0000u, 46u}, // pnn -> Latn
+    {0xB94F0000u, 45u}, // pko -> Latn
+    {0x706C0000u, 45u}, // pl -> Latn
+    {0x816F0000u, 45u}, // pla -> Latn
+    {0xC98F0000u, 45u}, // pms -> Latn
+    {0x99AF0000u, 45u}, // png -> Latn
+    {0xB5AF0000u, 45u}, // pnn -> Latn
     {0xCDAF0000u, 26u}, // pnt -> Grek
-    {0xB5CF0000u, 46u}, // pon -> Latn
+    {0xB5CF0000u, 45u}, // pon -> Latn
     {0x81EF0000u, 19u}, // ppa -> Deva
-    {0xB9EF0000u, 46u}, // ppo -> Latn
-    {0x822F0000u, 39u}, // pra -> Khar
+    {0xB9EF0000u, 45u}, // ppo -> Latn
+    {0x822F0000u, 38u}, // pra -> Khar
     {0x8E2F0000u,  2u}, // prd -> Arab
-    {0x9A2F0000u, 46u}, // prg -> Latn
+    {0x9A2F0000u, 45u}, // prg -> Latn
     {0x70730000u,  2u}, // ps -> Arab
-    {0xCA4F0000u, 46u}, // pss -> Latn
-    {0x70740000u, 46u}, // pt -> Latn
-    {0xBE6F0000u, 46u}, // ptp -> Latn
-    {0xD28F0000u, 46u}, // puu -> Latn
-    {0x82CF0000u, 46u}, // pwa -> Latn
-    {0x71750000u, 46u}, // qu -> Latn
-    {0x8A900000u, 46u}, // quc -> Latn
-    {0x9A900000u, 46u}, // qug -> Latn
-    {0xA0110000u, 46u}, // rai -> Latn
+    {0xCA4F0000u, 45u}, // pss -> Latn
+    {0x70740000u, 45u}, // pt -> Latn
+    {0xBE6F0000u, 45u}, // ptp -> Latn
+    {0xD28F0000u, 45u}, // puu -> Latn
+    {0x82CF0000u, 45u}, // pwa -> Latn
+    {0x71750000u, 45u}, // qu -> Latn
+    {0x8A900000u, 45u}, // quc -> Latn
+    {0x9A900000u, 45u}, // qug -> Latn
+    {0xA0110000u, 45u}, // rai -> Latn
     {0xA4110000u, 19u}, // raj -> Deva
-    {0xB8110000u, 46u}, // rao -> Latn
-    {0x94510000u, 46u}, // rcf -> Latn
-    {0xA4910000u, 46u}, // rej -> Latn
-    {0xAC910000u, 46u}, // rel -> Latn
-    {0xC8910000u, 46u}, // res -> Latn
-    {0xB4D10000u, 46u}, // rgn -> Latn
-    {0x98F10000u,  2u}, // rhg -> Arab
-    {0x81110000u, 46u}, // ria -> Latn
-    {0x95110000u, 89u}, // rif -> Tfng
-    {0x95114E4Cu, 46u}, // rif-NL -> Latn
+    {0xB8110000u, 45u}, // rao -> Latn
+    {0x94510000u, 45u}, // rcf -> Latn
+    {0xA4910000u, 45u}, // rej -> Latn
+    {0xAC910000u, 45u}, // rel -> Latn
+    {0xC8910000u, 45u}, // res -> Latn
+    {0xB4D10000u, 45u}, // rgn -> Latn
+    {0x98F10000u, 73u}, // rhg -> Rohg
+    {0x81110000u, 45u}, // ria -> Latn
+    {0x95110000u, 90u}, // rif -> Tfng
+    {0x95114E4Cu, 45u}, // rif-NL -> Latn
     {0xC9310000u, 19u}, // rjs -> Deva
     {0xCD510000u,  8u}, // rkt -> Beng
-    {0x726D0000u, 46u}, // rm -> Latn
-    {0x95910000u, 46u}, // rmf -> Latn
-    {0xB9910000u, 46u}, // rmo -> Latn
+    {0x726D0000u, 45u}, // rm -> Latn
+    {0x95910000u, 45u}, // rmf -> Latn
+    {0xB9910000u, 45u}, // rmo -> Latn
     {0xCD910000u,  2u}, // rmt -> Arab
-    {0xD1910000u, 46u}, // rmu -> Latn
-    {0x726E0000u, 46u}, // rn -> Latn
-    {0x81B10000u, 46u}, // rna -> Latn
-    {0x99B10000u, 46u}, // rng -> Latn
-    {0x726F0000u, 46u}, // ro -> Latn
-    {0x85D10000u, 46u}, // rob -> Latn
-    {0x95D10000u, 46u}, // rof -> Latn
-    {0xB9D10000u, 46u}, // roo -> Latn
-    {0xBA310000u, 46u}, // rro -> Latn
-    {0xB2710000u, 46u}, // rtm -> Latn
+    {0xD1910000u, 45u}, // rmu -> Latn
+    {0x726E0000u, 45u}, // rn -> Latn
+    {0x81B10000u, 45u}, // rna -> Latn
+    {0x99B10000u, 45u}, // rng -> Latn
+    {0x726F0000u, 45u}, // ro -> Latn
+    {0x85D10000u, 45u}, // rob -> Latn
+    {0x95D10000u, 45u}, // rof -> Latn
+    {0xB9D10000u, 45u}, // roo -> Latn
+    {0xBA310000u, 45u}, // rro -> Latn
+    {0xB2710000u, 45u}, // rtm -> Latn
     {0x72750000u, 18u}, // ru -> Cyrl
     {0x92910000u, 18u}, // rue -> Cyrl
-    {0x9A910000u, 46u}, // rug -> Latn
-    {0x72770000u, 46u}, // rw -> Latn
-    {0xAAD10000u, 46u}, // rwk -> Latn
-    {0xBAD10000u, 46u}, // rwo -> Latn
-    {0xD3110000u, 38u}, // ryu -> Kana
+    {0x9A910000u, 45u}, // rug -> Latn
+    {0x72770000u, 45u}, // rw -> Latn
+    {0xAAD10000u, 45u}, // rwk -> Latn
+    {0xBAD10000u, 45u}, // rwo -> Latn
+    {0xD3110000u, 37u}, // ryu -> Kana
     {0x73610000u, 19u}, // sa -> Deva
-    {0x94120000u, 46u}, // saf -> Latn
+    {0x94120000u, 45u}, // saf -> Latn
     {0x9C120000u, 18u}, // sah -> Cyrl
-    {0xC0120000u, 46u}, // saq -> Latn
-    {0xC8120000u, 46u}, // sas -> Latn
-    {0xCC120000u, 64u}, // sat -> Olck
-    {0xD4120000u, 46u}, // sav -> Latn
-    {0xE4120000u, 76u}, // saz -> Saur
-    {0x80320000u, 46u}, // sba -> Latn
-    {0x90320000u, 46u}, // sbe -> Latn
-    {0xBC320000u, 46u}, // sbp -> Latn
-    {0x73630000u, 46u}, // sc -> Latn
+    {0xC0120000u, 45u}, // saq -> Latn
+    {0xC8120000u, 45u}, // sas -> Latn
+    {0xCC120000u, 63u}, // sat -> Olck
+    {0xD4120000u, 45u}, // sav -> Latn
+    {0xE4120000u, 77u}, // saz -> Saur
+    {0x80320000u, 45u}, // sba -> Latn
+    {0x90320000u, 45u}, // sbe -> Latn
+    {0xBC320000u, 45u}, // sbp -> Latn
+    {0x73630000u, 45u}, // sc -> Latn
     {0xA8520000u, 19u}, // sck -> Deva
     {0xAC520000u,  2u}, // scl -> Arab
-    {0xB4520000u, 46u}, // scn -> Latn
-    {0xB8520000u, 46u}, // sco -> Latn
-    {0xC8520000u, 46u}, // scs -> Latn
+    {0xB4520000u, 45u}, // scn -> Latn
+    {0xB8520000u, 45u}, // sco -> Latn
+    {0xC8520000u, 45u}, // scs -> Latn
     {0x73640000u,  2u}, // sd -> Arab
-    {0x88720000u, 46u}, // sdc -> Latn
+    {0x88720000u, 45u}, // sdc -> Latn
     {0x9C720000u,  2u}, // sdh -> Arab
-    {0x73650000u, 46u}, // se -> Latn
-    {0x94920000u, 46u}, // sef -> Latn
-    {0x9C920000u, 46u}, // seh -> Latn
-    {0xA0920000u, 46u}, // sei -> Latn
-    {0xC8920000u, 46u}, // ses -> Latn
-    {0x73670000u, 46u}, // sg -> Latn
-    {0x80D20000u, 63u}, // sga -> Ogam
-    {0xC8D20000u, 46u}, // sgs -> Latn
+    {0x73650000u, 45u}, // se -> Latn
+    {0x94920000u, 45u}, // sef -> Latn
+    {0x9C920000u, 45u}, // seh -> Latn
+    {0xA0920000u, 45u}, // sei -> Latn
+    {0xC8920000u, 45u}, // ses -> Latn
+    {0x73670000u, 45u}, // sg -> Latn
+    {0x80D20000u, 62u}, // sga -> Ogam
+    {0xC8D20000u, 45u}, // sgs -> Latn
     {0xD8D20000u, 21u}, // sgw -> Ethi
-    {0xE4D20000u, 46u}, // sgz -> Latn
-    {0x73680000u, 46u}, // sh -> Latn
-    {0xA0F20000u, 89u}, // shi -> Tfng
-    {0xA8F20000u, 46u}, // shk -> Latn
-    {0xB4F20000u, 59u}, // shn -> Mymr
+    {0xE4D20000u, 45u}, // sgz -> Latn
+    {0x73680000u, 45u}, // sh -> Latn
+    {0xA0F20000u, 90u}, // shi -> Tfng
+    {0xA8F20000u, 45u}, // shk -> Latn
+    {0xB4F20000u, 58u}, // shn -> Mymr
     {0xD0F20000u,  2u}, // shu -> Arab
-    {0x73690000u, 78u}, // si -> Sinh
-    {0x8D120000u, 46u}, // sid -> Latn
-    {0x99120000u, 46u}, // sig -> Latn
-    {0xAD120000u, 46u}, // sil -> Latn
-    {0xB1120000u, 46u}, // sim -> Latn
-    {0xC5320000u, 46u}, // sjr -> Latn
-    {0x736B0000u, 46u}, // sk -> Latn
-    {0x89520000u, 46u}, // skc -> Latn
+    {0x73690000u, 79u}, // si -> Sinh
+    {0x8D120000u, 45u}, // sid -> Latn
+    {0x99120000u, 45u}, // sig -> Latn
+    {0xAD120000u, 45u}, // sil -> Latn
+    {0xB1120000u, 45u}, // sim -> Latn
+    {0xC5320000u, 45u}, // sjr -> Latn
+    {0x736B0000u, 45u}, // sk -> Latn
+    {0x89520000u, 45u}, // skc -> Latn
     {0xC5520000u,  2u}, // skr -> Arab
-    {0xC9520000u, 46u}, // sks -> Latn
-    {0x736C0000u, 46u}, // sl -> Latn
-    {0x8D720000u, 46u}, // sld -> Latn
-    {0xA1720000u, 46u}, // sli -> Latn
-    {0xAD720000u, 46u}, // sll -> Latn
-    {0xE1720000u, 46u}, // sly -> Latn
-    {0x736D0000u, 46u}, // sm -> Latn
-    {0x81920000u, 46u}, // sma -> Latn
-    {0xA5920000u, 46u}, // smj -> Latn
-    {0xB5920000u, 46u}, // smn -> Latn
-    {0xBD920000u, 74u}, // smp -> Samr
-    {0xC1920000u, 46u}, // smq -> Latn
-    {0xC9920000u, 46u}, // sms -> Latn
-    {0x736E0000u, 46u}, // sn -> Latn
-    {0x89B20000u, 46u}, // snc -> Latn
-    {0xA9B20000u, 46u}, // snk -> Latn
-    {0xBDB20000u, 46u}, // snp -> Latn
-    {0xDDB20000u, 46u}, // snx -> Latn
-    {0xE1B20000u, 46u}, // sny -> Latn
-    {0x736F0000u, 46u}, // so -> Latn
-    {0x99D20000u, 79u}, // sog -> Sogd
-    {0xA9D20000u, 46u}, // sok -> Latn
-    {0xC1D20000u, 46u}, // soq -> Latn
-    {0xD1D20000u, 91u}, // sou -> Thai
-    {0xE1D20000u, 46u}, // soy -> Latn
-    {0x8DF20000u, 46u}, // spd -> Latn
-    {0xADF20000u, 46u}, // spl -> Latn
-    {0xC9F20000u, 46u}, // sps -> Latn
-    {0x73710000u, 46u}, // sq -> Latn
+    {0xC9520000u, 45u}, // sks -> Latn
+    {0x736C0000u, 45u}, // sl -> Latn
+    {0x8D720000u, 45u}, // sld -> Latn
+    {0xA1720000u, 45u}, // sli -> Latn
+    {0xAD720000u, 45u}, // sll -> Latn
+    {0xE1720000u, 45u}, // sly -> Latn
+    {0x736D0000u, 45u}, // sm -> Latn
+    {0x81920000u, 45u}, // sma -> Latn
+    {0xA5920000u, 45u}, // smj -> Latn
+    {0xB5920000u, 45u}, // smn -> Latn
+    {0xBD920000u, 75u}, // smp -> Samr
+    {0xC1920000u, 45u}, // smq -> Latn
+    {0xC9920000u, 45u}, // sms -> Latn
+    {0x736E0000u, 45u}, // sn -> Latn
+    {0x89B20000u, 45u}, // snc -> Latn
+    {0xA9B20000u, 45u}, // snk -> Latn
+    {0xBDB20000u, 45u}, // snp -> Latn
+    {0xDDB20000u, 45u}, // snx -> Latn
+    {0xE1B20000u, 45u}, // sny -> Latn
+    {0x736F0000u, 45u}, // so -> Latn
+    {0x99D20000u, 80u}, // sog -> Sogd
+    {0xA9D20000u, 45u}, // sok -> Latn
+    {0xC1D20000u, 45u}, // soq -> Latn
+    {0xD1D20000u, 92u}, // sou -> Thai
+    {0xE1D20000u, 45u}, // soy -> Latn
+    {0x8DF20000u, 45u}, // spd -> Latn
+    {0xADF20000u, 45u}, // spl -> Latn
+    {0xC9F20000u, 45u}, // sps -> Latn
+    {0x73710000u, 45u}, // sq -> Latn
     {0x73720000u, 18u}, // sr -> Cyrl
-    {0x73724D45u, 46u}, // sr-ME -> Latn
-    {0x7372524Fu, 46u}, // sr-RO -> Latn
-    {0x73725255u, 46u}, // sr-RU -> Latn
-    {0x73725452u, 46u}, // sr-TR -> Latn
-    {0x86320000u, 80u}, // srb -> Sora
-    {0xB6320000u, 46u}, // srn -> Latn
-    {0xC6320000u, 46u}, // srr -> Latn
+    {0x73724D45u, 45u}, // sr-ME -> Latn
+    {0x7372524Fu, 45u}, // sr-RO -> Latn
+    {0x73725255u, 45u}, // sr-RU -> Latn
+    {0x73725452u, 45u}, // sr-TR -> Latn
+    {0x86320000u, 81u}, // srb -> Sora
+    {0xB6320000u, 45u}, // srn -> Latn
+    {0xC6320000u, 45u}, // srr -> Latn
     {0xDE320000u, 19u}, // srx -> Deva
-    {0x73730000u, 46u}, // ss -> Latn
-    {0x8E520000u, 46u}, // ssd -> Latn
-    {0x9A520000u, 46u}, // ssg -> Latn
-    {0xE2520000u, 46u}, // ssy -> Latn
-    {0x73740000u, 46u}, // st -> Latn
-    {0xAA720000u, 46u}, // stk -> Latn
-    {0xC2720000u, 46u}, // stq -> Latn
-    {0x73750000u, 46u}, // su -> Latn
-    {0x82920000u, 46u}, // sua -> Latn
-    {0x92920000u, 46u}, // sue -> Latn
-    {0xAA920000u, 46u}, // suk -> Latn
-    {0xC6920000u, 46u}, // sur -> Latn
-    {0xCA920000u, 46u}, // sus -> Latn
-    {0x73760000u, 46u}, // sv -> Latn
-    {0x73770000u, 46u}, // sw -> Latn
+    {0x73730000u, 45u}, // ss -> Latn
+    {0x8E520000u, 45u}, // ssd -> Latn
+    {0x9A520000u, 45u}, // ssg -> Latn
+    {0xE2520000u, 45u}, // ssy -> Latn
+    {0x73740000u, 45u}, // st -> Latn
+    {0xAA720000u, 45u}, // stk -> Latn
+    {0xC2720000u, 45u}, // stq -> Latn
+    {0x73750000u, 45u}, // su -> Latn
+    {0x82920000u, 45u}, // sua -> Latn
+    {0x92920000u, 45u}, // sue -> Latn
+    {0xAA920000u, 45u}, // suk -> Latn
+    {0xC6920000u, 45u}, // sur -> Latn
+    {0xCA920000u, 45u}, // sus -> Latn
+    {0x73760000u, 45u}, // sv -> Latn
+    {0x73770000u, 45u}, // sw -> Latn
     {0x86D20000u,  2u}, // swb -> Arab
-    {0x8AD20000u, 46u}, // swc -> Latn
-    {0x9AD20000u, 46u}, // swg -> Latn
-    {0xBED20000u, 46u}, // swp -> Latn
+    {0x8AD20000u, 45u}, // swc -> Latn
+    {0x9AD20000u, 45u}, // swg -> Latn
+    {0xBED20000u, 45u}, // swp -> Latn
     {0xD6D20000u, 19u}, // swv -> Deva
-    {0xB6F20000u, 46u}, // sxn -> Latn
-    {0xDAF20000u, 46u}, // sxw -> Latn
+    {0xB6F20000u, 45u}, // sxn -> Latn
+    {0xDAF20000u, 45u}, // sxw -> Latn
     {0xAF120000u,  8u}, // syl -> Beng
-    {0xC7120000u, 82u}, // syr -> Syrc
-    {0xAF320000u, 46u}, // szl -> Latn
-    {0x74610000u, 85u}, // ta -> Taml
+    {0xC7120000u, 83u}, // syr -> Syrc
+    {0xAF320000u, 45u}, // szl -> Latn
+    {0x74610000u, 86u}, // ta -> Taml
     {0xA4130000u, 19u}, // taj -> Deva
-    {0xAC130000u, 46u}, // tal -> Latn
-    {0xB4130000u, 46u}, // tan -> Latn
-    {0xC0130000u, 46u}, // taq -> Latn
-    {0x88330000u, 46u}, // tbc -> Latn
-    {0x8C330000u, 46u}, // tbd -> Latn
-    {0x94330000u, 46u}, // tbf -> Latn
-    {0x98330000u, 46u}, // tbg -> Latn
-    {0xB8330000u, 46u}, // tbo -> Latn
-    {0xD8330000u, 46u}, // tbw -> Latn
-    {0xE4330000u, 46u}, // tbz -> Latn
-    {0xA0530000u, 46u}, // tci -> Latn
-    {0xE0530000u, 42u}, // tcy -> Knda
-    {0x8C730000u, 83u}, // tdd -> Tale
+    {0xAC130000u, 45u}, // tal -> Latn
+    {0xB4130000u, 45u}, // tan -> Latn
+    {0xC0130000u, 45u}, // taq -> Latn
+    {0x88330000u, 45u}, // tbc -> Latn
+    {0x8C330000u, 45u}, // tbd -> Latn
+    {0x94330000u, 45u}, // tbf -> Latn
+    {0x98330000u, 45u}, // tbg -> Latn
+    {0xB8330000u, 45u}, // tbo -> Latn
+    {0xD8330000u, 45u}, // tbw -> Latn
+    {0xE4330000u, 45u}, // tbz -> Latn
+    {0xA0530000u, 45u}, // tci -> Latn
+    {0xE0530000u, 41u}, // tcy -> Knda
+    {0x8C730000u, 84u}, // tdd -> Tale
     {0x98730000u, 19u}, // tdg -> Deva
     {0x9C730000u, 19u}, // tdh -> Deva
-    {0xD0730000u, 46u}, // tdu -> Latn
-    {0x74650000u, 88u}, // te -> Telu
-    {0x8C930000u, 46u}, // ted -> Latn
-    {0xB0930000u, 46u}, // tem -> Latn
-    {0xB8930000u, 46u}, // teo -> Latn
-    {0xCC930000u, 46u}, // tet -> Latn
-    {0xA0B30000u, 46u}, // tfi -> Latn
+    {0xD0730000u, 45u}, // tdu -> Latn
+    {0x74650000u, 89u}, // te -> Telu
+    {0x8C930000u, 45u}, // ted -> Latn
+    {0xB0930000u, 45u}, // tem -> Latn
+    {0xB8930000u, 45u}, // teo -> Latn
+    {0xCC930000u, 45u}, // tet -> Latn
+    {0xA0B30000u, 45u}, // tfi -> Latn
     {0x74670000u, 18u}, // tg -> Cyrl
     {0x7467504Bu,  2u}, // tg-PK -> Arab
-    {0x88D30000u, 46u}, // tgc -> Latn
-    {0xB8D30000u, 46u}, // tgo -> Latn
-    {0xD0D30000u, 46u}, // tgu -> Latn
-    {0x74680000u, 91u}, // th -> Thai
+    {0x88D30000u, 45u}, // tgc -> Latn
+    {0xB8D30000u, 45u}, // tgo -> Latn
+    {0xD0D30000u, 45u}, // tgu -> Latn
+    {0x74680000u, 92u}, // th -> Thai
     {0xACF30000u, 19u}, // thl -> Deva
     {0xC0F30000u, 19u}, // thq -> Deva
     {0xC4F30000u, 19u}, // thr -> Deva
     {0x74690000u, 21u}, // ti -> Ethi
-    {0x95130000u, 46u}, // tif -> Latn
+    {0x95130000u, 45u}, // tif -> Latn
     {0x99130000u, 21u}, // tig -> Ethi
-    {0xA9130000u, 46u}, // tik -> Latn
-    {0xB1130000u, 46u}, // tim -> Latn
-    {0xB9130000u, 46u}, // tio -> Latn
-    {0xD5130000u, 46u}, // tiv -> Latn
-    {0x746B0000u, 46u}, // tk -> Latn
-    {0xAD530000u, 46u}, // tkl -> Latn
-    {0xC5530000u, 46u}, // tkr -> Latn
+    {0xA9130000u, 45u}, // tik -> Latn
+    {0xB1130000u, 45u}, // tim -> Latn
+    {0xB9130000u, 45u}, // tio -> Latn
+    {0xD5130000u, 45u}, // tiv -> Latn
+    {0x746B0000u, 45u}, // tk -> Latn
+    {0xAD530000u, 45u}, // tkl -> Latn
+    {0xC5530000u, 45u}, // tkr -> Latn
     {0xCD530000u, 19u}, // tkt -> Deva
-    {0x746C0000u, 46u}, // tl -> Latn
-    {0x95730000u, 46u}, // tlf -> Latn
-    {0xDD730000u, 46u}, // tlx -> Latn
-    {0xE1730000u, 46u}, // tly -> Latn
-    {0x9D930000u, 46u}, // tmh -> Latn
-    {0xE1930000u, 46u}, // tmy -> Latn
-    {0x746E0000u, 46u}, // tn -> Latn
-    {0x9DB30000u, 46u}, // tnh -> Latn
-    {0x746F0000u, 46u}, // to -> Latn
-    {0x95D30000u, 46u}, // tof -> Latn
-    {0x99D30000u, 46u}, // tog -> Latn
-    {0xC1D30000u, 46u}, // toq -> Latn
-    {0xA1F30000u, 46u}, // tpi -> Latn
-    {0xB1F30000u, 46u}, // tpm -> Latn
-    {0xE5F30000u, 46u}, // tpz -> Latn
-    {0xBA130000u, 46u}, // tqo -> Latn
-    {0x74720000u, 46u}, // tr -> Latn
-    {0xD2330000u, 46u}, // tru -> Latn
-    {0xD6330000u, 46u}, // trv -> Latn
+    {0x746C0000u, 45u}, // tl -> Latn
+    {0x95730000u, 45u}, // tlf -> Latn
+    {0xDD730000u, 45u}, // tlx -> Latn
+    {0xE1730000u, 45u}, // tly -> Latn
+    {0x9D930000u, 45u}, // tmh -> Latn
+    {0xE1930000u, 45u}, // tmy -> Latn
+    {0x746E0000u, 45u}, // tn -> Latn
+    {0x9DB30000u, 45u}, // tnh -> Latn
+    {0x746F0000u, 45u}, // to -> Latn
+    {0x95D30000u, 45u}, // tof -> Latn
+    {0x99D30000u, 45u}, // tog -> Latn
+    {0xC1D30000u, 45u}, // toq -> Latn
+    {0xA1F30000u, 45u}, // tpi -> Latn
+    {0xB1F30000u, 45u}, // tpm -> Latn
+    {0xE5F30000u, 45u}, // tpz -> Latn
+    {0xBA130000u, 45u}, // tqo -> Latn
+    {0x74720000u, 45u}, // tr -> Latn
+    {0xD2330000u, 45u}, // tru -> Latn
+    {0xD6330000u, 45u}, // trv -> Latn
     {0xDA330000u,  2u}, // trw -> Arab
-    {0x74730000u, 46u}, // ts -> Latn
+    {0x74730000u, 45u}, // ts -> Latn
     {0x8E530000u, 26u}, // tsd -> Grek
     {0x96530000u, 19u}, // tsf -> Deva
-    {0x9A530000u, 46u}, // tsg -> Latn
-    {0xA6530000u, 92u}, // tsj -> Tibt
-    {0xDA530000u, 46u}, // tsw -> Latn
+    {0x9A530000u, 45u}, // tsg -> Latn
+    {0xA6530000u, 93u}, // tsj -> Tibt
+    {0xDA530000u, 45u}, // tsw -> Latn
     {0x74740000u, 18u}, // tt -> Cyrl
-    {0x8E730000u, 46u}, // ttd -> Latn
-    {0x92730000u, 46u}, // tte -> Latn
-    {0xA6730000u, 46u}, // ttj -> Latn
-    {0xC6730000u, 46u}, // ttr -> Latn
-    {0xCA730000u, 91u}, // tts -> Thai
-    {0xCE730000u, 46u}, // ttt -> Latn
-    {0x9E930000u, 46u}, // tuh -> Latn
-    {0xAE930000u, 46u}, // tul -> Latn
-    {0xB2930000u, 46u}, // tum -> Latn
-    {0xC2930000u, 46u}, // tuq -> Latn
-    {0x8EB30000u, 46u}, // tvd -> Latn
-    {0xAEB30000u, 46u}, // tvl -> Latn
-    {0xD2B30000u, 46u}, // tvu -> Latn
-    {0x9ED30000u, 46u}, // twh -> Latn
-    {0xC2D30000u, 46u}, // twq -> Latn
-    {0x9AF30000u, 86u}, // txg -> Tang
-    {0x74790000u, 46u}, // ty -> Latn
-    {0x83130000u, 46u}, // tya -> Latn
+    {0x8E730000u, 45u}, // ttd -> Latn
+    {0x92730000u, 45u}, // tte -> Latn
+    {0xA6730000u, 45u}, // ttj -> Latn
+    {0xC6730000u, 45u}, // ttr -> Latn
+    {0xCA730000u, 92u}, // tts -> Thai
+    {0xCE730000u, 45u}, // ttt -> Latn
+    {0x9E930000u, 45u}, // tuh -> Latn
+    {0xAE930000u, 45u}, // tul -> Latn
+    {0xB2930000u, 45u}, // tum -> Latn
+    {0xC2930000u, 45u}, // tuq -> Latn
+    {0x8EB30000u, 45u}, // tvd -> Latn
+    {0xAEB30000u, 45u}, // tvl -> Latn
+    {0xD2B30000u, 45u}, // tvu -> Latn
+    {0x9ED30000u, 45u}, // twh -> Latn
+    {0xC2D30000u, 45u}, // twq -> Latn
+    {0x9AF30000u, 87u}, // txg -> Tang
+    {0xBAF30000u, 95u}, // txo -> Toto
+    {0x74790000u, 45u}, // ty -> Latn
+    {0x83130000u, 45u}, // tya -> Latn
     {0xD7130000u, 18u}, // tyv -> Cyrl
-    {0xB3330000u, 46u}, // tzm -> Latn
-    {0xD0340000u, 46u}, // ubu -> Latn
+    {0xB3330000u, 45u}, // tzm -> Latn
+    {0xD0340000u, 45u}, // ubu -> Latn
     {0xA0740000u,  0u}, // udi -> Aghb
     {0xB0740000u, 18u}, // udm -> Cyrl
     {0x75670000u,  2u}, // ug -> Arab
     {0x75674B5Au, 18u}, // ug-KZ -> Cyrl
     {0x75674D4Eu, 18u}, // ug-MN -> Cyrl
-    {0x80D40000u, 93u}, // uga -> Ugar
+    {0x80D40000u, 96u}, // uga -> Ugar
     {0x756B0000u, 18u}, // uk -> Cyrl
-    {0xA1740000u, 46u}, // uli -> Latn
-    {0x85940000u, 46u}, // umb -> Latn
+    {0xA1740000u, 45u}, // uli -> Latn
+    {0x85940000u, 45u}, // umb -> Latn
     {0xC5B40000u,  8u}, // unr -> Beng
     {0xC5B44E50u, 19u}, // unr-NP -> Deva
     {0xDDB40000u,  8u}, // unx -> Beng
-    {0xA9D40000u, 46u}, // uok -> Latn
+    {0xA9D40000u, 45u}, // uok -> Latn
     {0x75720000u,  2u}, // ur -> Arab
-    {0xA2340000u, 46u}, // uri -> Latn
-    {0xCE340000u, 46u}, // urt -> Latn
-    {0xDA340000u, 46u}, // urw -> Latn
-    {0x82540000u, 46u}, // usa -> Latn
-    {0x9E740000u, 46u}, // uth -> Latn
-    {0xC6740000u, 46u}, // utr -> Latn
-    {0x9EB40000u, 46u}, // uvh -> Latn
-    {0xAEB40000u, 46u}, // uvl -> Latn
-    {0x757A0000u, 46u}, // uz -> Latn
+    {0xA2340000u, 45u}, // uri -> Latn
+    {0xCE340000u, 45u}, // urt -> Latn
+    {0xDA340000u, 45u}, // urw -> Latn
+    {0x82540000u, 45u}, // usa -> Latn
+    {0x9E740000u, 45u}, // uth -> Latn
+    {0xC6740000u, 45u}, // utr -> Latn
+    {0x9EB40000u, 45u}, // uvh -> Latn
+    {0xAEB40000u, 45u}, // uvl -> Latn
+    {0x757A0000u, 45u}, // uz -> Latn
     {0x757A4146u,  2u}, // uz-AF -> Arab
     {0x757A434Eu, 18u}, // uz-CN -> Cyrl
-    {0x98150000u, 46u}, // vag -> Latn
-    {0xA0150000u, 94u}, // vai -> Vaii
-    {0xB4150000u, 46u}, // van -> Latn
-    {0x76650000u, 46u}, // ve -> Latn
-    {0x88950000u, 46u}, // vec -> Latn
-    {0xBC950000u, 46u}, // vep -> Latn
-    {0x76690000u, 46u}, // vi -> Latn
-    {0x89150000u, 46u}, // vic -> Latn
-    {0xD5150000u, 46u}, // viv -> Latn
-    {0xC9750000u, 46u}, // vls -> Latn
-    {0x95950000u, 46u}, // vmf -> Latn
-    {0xD9950000u, 46u}, // vmw -> Latn
-    {0x766F0000u, 46u}, // vo -> Latn
-    {0xCDD50000u, 46u}, // vot -> Latn
-    {0xBA350000u, 46u}, // vro -> Latn
-    {0xB6950000u, 46u}, // vun -> Latn
-    {0xCE950000u, 46u}, // vut -> Latn
-    {0x77610000u, 46u}, // wa -> Latn
-    {0x90160000u, 46u}, // wae -> Latn
-    {0xA4160000u, 46u}, // waj -> Latn
+    {0x98150000u, 45u}, // vag -> Latn
+    {0xA0150000u, 97u}, // vai -> Vaii
+    {0xB4150000u, 45u}, // van -> Latn
+    {0x76650000u, 45u}, // ve -> Latn
+    {0x88950000u, 45u}, // vec -> Latn
+    {0xBC950000u, 45u}, // vep -> Latn
+    {0x76690000u, 45u}, // vi -> Latn
+    {0x89150000u, 45u}, // vic -> Latn
+    {0xD5150000u, 45u}, // viv -> Latn
+    {0xC9750000u, 45u}, // vls -> Latn
+    {0x95950000u, 45u}, // vmf -> Latn
+    {0xD9950000u, 45u}, // vmw -> Latn
+    {0x766F0000u, 45u}, // vo -> Latn
+    {0xCDD50000u, 45u}, // vot -> Latn
+    {0xBA350000u, 45u}, // vro -> Latn
+    {0xB6950000u, 45u}, // vun -> Latn
+    {0xCE950000u, 45u}, // vut -> Latn
+    {0x77610000u, 45u}, // wa -> Latn
+    {0x90160000u, 45u}, // wae -> Latn
+    {0xA4160000u, 45u}, // waj -> Latn
     {0xAC160000u, 21u}, // wal -> Ethi
-    {0xB4160000u, 46u}, // wan -> Latn
-    {0xC4160000u, 46u}, // war -> Latn
-    {0xBC360000u, 46u}, // wbp -> Latn
-    {0xC0360000u, 88u}, // wbq -> Telu
+    {0xB4160000u, 45u}, // wan -> Latn
+    {0xC4160000u, 45u}, // war -> Latn
+    {0xBC360000u, 45u}, // wbp -> Latn
+    {0xC0360000u, 89u}, // wbq -> Telu
     {0xC4360000u, 19u}, // wbr -> Deva
-    {0xA0560000u, 46u}, // wci -> Latn
-    {0xC4960000u, 46u}, // wer -> Latn
-    {0xA0D60000u, 46u}, // wgi -> Latn
-    {0x98F60000u, 46u}, // whg -> Latn
-    {0x85160000u, 46u}, // wib -> Latn
-    {0xD1160000u, 46u}, // wiu -> Latn
-    {0xD5160000u, 46u}, // wiv -> Latn
-    {0x81360000u, 46u}, // wja -> Latn
-    {0xA1360000u, 46u}, // wji -> Latn
-    {0xC9760000u, 46u}, // wls -> Latn
-    {0xB9960000u, 46u}, // wmo -> Latn
-    {0x89B60000u, 46u}, // wnc -> Latn
+    {0xA0560000u, 45u}, // wci -> Latn
+    {0xC4960000u, 45u}, // wer -> Latn
+    {0xA0D60000u, 45u}, // wgi -> Latn
+    {0x98F60000u, 45u}, // whg -> Latn
+    {0x85160000u, 45u}, // wib -> Latn
+    {0xD1160000u, 45u}, // wiu -> Latn
+    {0xD5160000u, 45u}, // wiv -> Latn
+    {0x81360000u, 45u}, // wja -> Latn
+    {0xA1360000u, 45u}, // wji -> Latn
+    {0xC9760000u, 45u}, // wls -> Latn
+    {0xB9960000u, 45u}, // wmo -> Latn
+    {0x89B60000u, 45u}, // wnc -> Latn
     {0xA1B60000u,  2u}, // wni -> Arab
-    {0xD1B60000u, 46u}, // wnu -> Latn
-    {0x776F0000u, 46u}, // wo -> Latn
-    {0x85D60000u, 46u}, // wob -> Latn
-    {0xC9D60000u, 46u}, // wos -> Latn
-    {0xCA360000u, 46u}, // wrs -> Latn
+    {0xD1B60000u, 45u}, // wnu -> Latn
+    {0x776F0000u, 45u}, // wo -> Latn
+    {0x85D60000u, 45u}, // wob -> Latn
+    {0xC9D60000u, 45u}, // wos -> Latn
+    {0xCA360000u, 45u}, // wrs -> Latn
     {0x9A560000u, 23u}, // wsg -> Gong
-    {0xAA560000u, 46u}, // wsk -> Latn
+    {0xAA560000u, 45u}, // wsk -> Latn
     {0xB2760000u, 19u}, // wtm -> Deva
     {0xD2960000u, 29u}, // wuu -> Hans
-    {0xD6960000u, 46u}, // wuv -> Latn
-    {0x82D60000u, 46u}, // wwa -> Latn
-    {0xD4170000u, 46u}, // xav -> Latn
-    {0xA0370000u, 46u}, // xbi -> Latn
+    {0xD6960000u, 45u}, // wuv -> Latn
+    {0x82D60000u, 45u}, // wwa -> Latn
+    {0xD4170000u, 45u}, // xav -> Latn
+    {0xA0370000u, 45u}, // xbi -> Latn
     {0xB8570000u, 15u}, // xco -> Chrs
     {0xC4570000u, 12u}, // xcr -> Cari
-    {0xC8970000u, 46u}, // xes -> Latn
-    {0x78680000u, 46u}, // xh -> Latn
-    {0x81770000u, 46u}, // xla -> Latn
-    {0x89770000u, 50u}, // xlc -> Lyci
-    {0x8D770000u, 51u}, // xld -> Lydi
+    {0xC8970000u, 45u}, // xes -> Latn
+    {0x78680000u, 45u}, // xh -> Latn
+    {0x81770000u, 45u}, // xla -> Latn
+    {0x89770000u, 49u}, // xlc -> Lyci
+    {0x8D770000u, 50u}, // xld -> Lydi
     {0x95970000u, 22u}, // xmf -> Geor
-    {0xB5970000u, 53u}, // xmn -> Mani
-    {0xC5970000u, 55u}, // xmr -> Merc
-    {0x81B70000u, 60u}, // xna -> Narb
+    {0xB5970000u, 52u}, // xmn -> Mani
+    {0xC5970000u, 54u}, // xmr -> Merc
+    {0x81B70000u, 59u}, // xna -> Narb
     {0xC5B70000u, 19u}, // xnr -> Deva
-    {0x99D70000u, 46u}, // xog -> Latn
-    {0xB5D70000u, 46u}, // xon -> Latn
+    {0x99D70000u, 45u}, // xog -> Latn
+    {0xB5D70000u, 45u}, // xon -> Latn
     {0xC5F70000u, 72u}, // xpr -> Prti
-    {0x86370000u, 46u}, // xrb -> Latn
-    {0x82570000u, 75u}, // xsa -> Sarb
-    {0xA2570000u, 46u}, // xsi -> Latn
-    {0xB2570000u, 46u}, // xsm -> Latn
+    {0x86370000u, 45u}, // xrb -> Latn
+    {0x82570000u, 76u}, // xsa -> Sarb
+    {0xA2570000u, 45u}, // xsi -> Latn
+    {0xB2570000u, 45u}, // xsm -> Latn
     {0xC6570000u, 19u}, // xsr -> Deva
-    {0x92D70000u, 46u}, // xwe -> Latn
-    {0xB0180000u, 46u}, // yam -> Latn
-    {0xB8180000u, 46u}, // yao -> Latn
-    {0xBC180000u, 46u}, // yap -> Latn
-    {0xC8180000u, 46u}, // yas -> Latn
-    {0xCC180000u, 46u}, // yat -> Latn
-    {0xD4180000u, 46u}, // yav -> Latn
-    {0xE0180000u, 46u}, // yay -> Latn
-    {0xE4180000u, 46u}, // yaz -> Latn
-    {0x80380000u, 46u}, // yba -> Latn
-    {0x84380000u, 46u}, // ybb -> Latn
-    {0xE0380000u, 46u}, // yby -> Latn
-    {0xC4980000u, 46u}, // yer -> Latn
-    {0xC4D80000u, 46u}, // ygr -> Latn
-    {0xD8D80000u, 46u}, // ygw -> Latn
+    {0x92D70000u, 45u}, // xwe -> Latn
+    {0xB0180000u, 45u}, // yam -> Latn
+    {0xB8180000u, 45u}, // yao -> Latn
+    {0xBC180000u, 45u}, // yap -> Latn
+    {0xC8180000u, 45u}, // yas -> Latn
+    {0xCC180000u, 45u}, // yat -> Latn
+    {0xD4180000u, 45u}, // yav -> Latn
+    {0xE0180000u, 45u}, // yay -> Latn
+    {0xE4180000u, 45u}, // yaz -> Latn
+    {0x80380000u, 45u}, // yba -> Latn
+    {0x84380000u, 45u}, // ybb -> Latn
+    {0xE0380000u, 45u}, // yby -> Latn
+    {0xC4980000u, 45u}, // yer -> Latn
+    {0xC4D80000u, 45u}, // ygr -> Latn
+    {0xD8D80000u, 45u}, // ygw -> Latn
     {0x79690000u, 31u}, // yi -> Hebr
-    {0xB9580000u, 46u}, // yko -> Latn
-    {0x91780000u, 46u}, // yle -> Latn
-    {0x99780000u, 46u}, // ylg -> Latn
-    {0xAD780000u, 46u}, // yll -> Latn
-    {0xAD980000u, 46u}, // yml -> Latn
-    {0x796F0000u, 46u}, // yo -> Latn
-    {0xB5D80000u, 46u}, // yon -> Latn
-    {0x86380000u, 46u}, // yrb -> Latn
-    {0x92380000u, 46u}, // yre -> Latn
-    {0xAE380000u, 46u}, // yrl -> Latn
-    {0xCA580000u, 46u}, // yss -> Latn
-    {0x82980000u, 46u}, // yua -> Latn
+    {0xB9580000u, 45u}, // yko -> Latn
+    {0x91780000u, 45u}, // yle -> Latn
+    {0x99780000u, 45u}, // ylg -> Latn
+    {0xAD780000u, 45u}, // yll -> Latn
+    {0xAD980000u, 45u}, // yml -> Latn
+    {0x796F0000u, 45u}, // yo -> Latn
+    {0xB5D80000u, 45u}, // yon -> Latn
+    {0x86380000u, 45u}, // yrb -> Latn
+    {0x92380000u, 45u}, // yre -> Latn
+    {0xAE380000u, 45u}, // yrl -> Latn
+    {0xCA580000u, 45u}, // yss -> Latn
+    {0x82980000u, 45u}, // yua -> Latn
     {0x92980000u, 30u}, // yue -> Hant
     {0x9298434Eu, 29u}, // yue-CN -> Hans
-    {0xA6980000u, 46u}, // yuj -> Latn
-    {0xCE980000u, 46u}, // yut -> Latn
-    {0xDA980000u, 46u}, // yuw -> Latn
-    {0x7A610000u, 46u}, // za -> Latn
-    {0x98190000u, 46u}, // zag -> Latn
+    {0xA6980000u, 45u}, // yuj -> Latn
+    {0xCE980000u, 45u}, // yut -> Latn
+    {0xDA980000u, 45u}, // yuw -> Latn
+    {0x7A610000u, 45u}, // za -> Latn
+    {0x98190000u, 45u}, // zag -> Latn
     {0xA4790000u,  2u}, // zdj -> Arab
-    {0x80990000u, 46u}, // zea -> Latn
-    {0x9CD90000u, 89u}, // zgh -> Tfng
+    {0x80990000u, 45u}, // zea -> Latn
+    {0x9CD90000u, 90u}, // zgh -> Tfng
     {0x7A680000u, 29u}, // zh -> Hans
     {0x7A684155u, 30u}, // zh-AU -> Hant
     {0x7A68424Eu, 30u}, // zh-BN -> Hant
@@ -1486,14 +1493,14 @@
     {0x7A685457u, 30u}, // zh-TW -> Hant
     {0x7A685553u, 30u}, // zh-US -> Hant
     {0x7A68564Eu, 30u}, // zh-VN -> Hant
-    {0xDCF90000u, 62u}, // zhx -> Nshu
-    {0x81190000u, 46u}, // zia -> Latn
-    {0xCD590000u, 41u}, // zkt -> Kits
-    {0xB1790000u, 46u}, // zlm -> Latn
-    {0xA1990000u, 46u}, // zmi -> Latn
-    {0x91B90000u, 46u}, // zne -> Latn
-    {0x7A750000u, 46u}, // zu -> Latn
-    {0x83390000u, 46u}, // zza -> Latn
+    {0xDCF90000u, 61u}, // zhx -> Nshu
+    {0x81190000u, 45u}, // zia -> Latn
+    {0xCD590000u, 40u}, // zkt -> Kits
+    {0xB1790000u, 45u}, // zlm -> Latn
+    {0xA1990000u, 45u}, // zmi -> Latn
+    {0x91B90000u, 45u}, // zne -> Latn
+    {0x7A750000u, 45u}, // zu -> Latn
+    {0x83390000u, 45u}, // zza -> Latn
 });
 
 std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
@@ -1573,6 +1580,7 @@
     0xCD21534E4C61746ELLU, // bjt_Latn_SN
     0xB141434D4C61746ELLU, // bkm_Latn_CM
     0xD14150484C61746ELLU, // bku_Latn_PH
+    0x99614D594C61746ELLU, // blg_Latn_MY
     0xCD61564E54617674LLU, // blt_Tavt_VN
     0x626D4D4C4C61746ELLU, // bm_Latn_ML
     0xC1814D4C4C61746ELLU, // bmq_Latn_ML
@@ -1748,7 +1756,7 @@
     0x8D87434E506C7264LLU, // hmd_Plrd_CN
     0x8DA7504B41726162LLU, // hnd_Arab_PK
     0x91A7494E44657661LLU, // hne_Deva_IN
-    0xA5A74C41486D6E67LLU, // hnj_Hmng_LA
+    0xA5A75553486D6E70LLU, // hnj_Hmnp_US
     0xB5A750484C61746ELLU, // hnn_Latn_PH
     0xB9A7504B41726162LLU, // hno_Arab_PK
     0x686F50474C61746ELLU, // ho_Latn_PG
@@ -1797,7 +1805,7 @@
     0x984A4E474C61746ELLU, // kcg_Latn_NG
     0xA84A5A574C61746ELLU, // kck_Latn_ZW
     0x906A545A4C61746ELLU, // kde_Latn_TZ
-    0x9C6A544741726162LLU, // kdh_Arab_TG
+    0x9C6A54474C61746ELLU, // kdh_Latn_TG
     0xCC6A544854686169LLU, // kdt_Thai_TH
     0x808A43564C61746ELLU, // kea_Latn_CV
     0xB48A434D4C61746ELLU, // ken_Latn_CM
@@ -1982,6 +1990,7 @@
     0x6E725A414C61746ELLU, // nr_Latn_ZA
     0xAA4D434143616E73LLU, // nsk_Cans_CA
     0xBA4D5A414C61746ELLU, // nso_Latn_ZA
+    0xCE4D494E546E7361LLU, // nst_Tnsa_IN
     0xCA8D53534C61746ELLU, // nus_Latn_SS
     0x6E7655534C61746ELLU, // nv_Latn_US
     0xC2ED434E4C61746ELLU, // nxq_Latn_CN
@@ -1995,6 +2004,7 @@
     0x6F7347454379726CLLU, // os_Cyrl_GE
     0x824E55534F736765LLU, // osa_Osge_US
     0xAA6E4D4E4F726B68LLU, // otk_Orkh_MN
+    0xA28E8C814F756772LLU, // oui_Ougr_143
     0x7061504B41726162LLU, // pa_Arab_PK
     0x7061494E47757275LLU, // pa_Guru_IN
     0x980F50484C61746ELLU, // pag_Latn_PH
@@ -2029,7 +2039,7 @@
     0x945152454C61746ELLU, // rcf_Latn_RE
     0xA49149444C61746ELLU, // rej_Latn_ID
     0xB4D149544C61746ELLU, // rgn_Latn_IT
-    0x98F14D4D41726162LLU, // rhg_Arab_MM
+    0x98F14D4D526F6867LLU, // rhg_Rohg_MM
     0x8111494E4C61746ELLU, // ria_Latn_IN
     0x95114D4154666E67LLU, // rif_Tfng_MA
     0xC9314E5044657661LLU, // rjs_Deva_NP
@@ -2172,6 +2182,7 @@
     0xAEB354564C61746ELLU, // tvl_Latn_TV
     0xC2D34E454C61746ELLU, // twq_Latn_NE
     0x9AF3434E54616E67LLU, // txg_Tang_CN
+    0xBAF3494E546F746FLLU, // txo_Toto_IN
     0x747950464C61746ELLU, // ty_Latn_PF
     0xD71352554379726CLLU, // tyv_Cyrl_RU
     0xB3334D414C61746ELLU, // tzm_Latn_MA
@@ -2256,6 +2267,7 @@
 });
 
 const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({
+    {0x61724145u, 0x61729420u}, // ar-AE -> ar-015
     {0x6172445Au, 0x61729420u}, // ar-DZ -> ar-015
     {0x61724548u, 0x61729420u}, // ar-EH -> ar-015
     {0x61724C59u, 0x61729420u}, // ar-LY -> ar-015
@@ -2279,7 +2291,6 @@
     {0x656E4253u, 0x656E8400u}, // en-BS -> en-001
     {0x656E4257u, 0x656E8400u}, // en-BW -> en-001
     {0x656E425Au, 0x656E8400u}, // en-BZ -> en-001
-    {0x656E4341u, 0x656E8400u}, // en-CA -> en-001
     {0x656E4343u, 0x656E8400u}, // en-CC -> en-001
     {0x656E4348u, 0x656E80A1u}, // en-CH -> en-150
     {0x656E434Bu, 0x656E8400u}, // en-CK -> en-001
@@ -2332,7 +2343,6 @@
     {0x656E4E55u, 0x656E8400u}, // en-NU -> en-001
     {0x656E4E5Au, 0x656E8400u}, // en-NZ -> en-001
     {0x656E5047u, 0x656E8400u}, // en-PG -> en-001
-    {0x656E5048u, 0x656E8400u}, // en-PH -> en-001
     {0x656E504Bu, 0x656E8400u}, // en-PK -> en-001
     {0x656E504Eu, 0x656E8400u}, // en-PN -> en-001
     {0x656E5057u, 0x656E8400u}, // en-PW -> en-001
diff --git a/libs/androidfw/TEST_MAPPING b/libs/androidfw/TEST_MAPPING
index 9ebc996..8abe79d 100644
--- a/libs/androidfw/TEST_MAPPING
+++ b/libs/androidfw/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "CtsResourcesLoaderTests"
+    },
+    {
+      "name": "libandroidfw_tests"
     }
   ]
 }
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index a3b42df..1bde792 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -192,6 +192,12 @@
   std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
                                       Asset::AccessMode mode) const;
 
+  // Returns the resource id of parent style of the specified theme.
+  //
+  // Returns a null error if the name is missing/corrupt, or an I/O error if reading resource data
+  // failed.
+  base::expected<uint32_t, NullOrIOError> GetParentThemeResourceId(uint32_t resid) const;
+
   // Returns the resource name of the specified resource ID.
   //
   // Utf8 strings are preferred, and only if they are unavailable are the Utf16 variants populated.
diff --git a/libs/hwui/ColorMode.h b/libs/hwui/ColorMode.h
index 6d387f9..3df5c3c 100644
--- a/libs/hwui/ColorMode.h
+++ b/libs/hwui/ColorMode.h
@@ -29,6 +29,8 @@
     Hdr = 2,
     // HDR Rec2020 + 1010102
     Hdr10 = 3,
+    // Alpha 8
+    A8 = 4,
 };
 
 } // namespace android::uirenderer
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 1b38920..c4366f75 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -273,21 +273,99 @@
     return ret;
 }
 
-static void RuntimeShader_updateUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
-                                         jstring jUniformName, jfloatArray jvalues) {
+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 UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder,
+                                const char* uniformName, const float values[], int count,
+                                bool isColor) {
+    SkRuntimeShaderBuilder::BuilderUniform 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 RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+                                              jstring jUniformName, jfloat value1, jfloat value2,
+                                              jfloat value3, jfloat value4, jint count) {
+    SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    const float values[4] = {value1, value2, value3, value4};
+    UpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+}
+
+static void RuntimeShader_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+                                                   jstring jUniformName, jfloatArray jvalues,
+                                                   jboolean isColor) {
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
     ScopedUtfChars name(env, jUniformName);
     AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
+    UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor);
+}
 
-    SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(name.c_str());
+static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName,
+                              const int values[], int count) {
+    SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName);
     if (uniform.fVar == nullptr) {
-        ThrowIAEFmt(env, "unable to find uniform named %s", name.c_str());
-    } else if (!uniform.set<float>(autoValues.ptr(), autoValues.length())) {
+        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) * autoValues.length());
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
     }
 }
 
+static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+                                            jstring jUniformName, jint value1, jint value2,
+                                            jint value3, jint value4, jint count) {
+    SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    const int values[4] = {value1, value2, value3, value4};
+    UpdateIntUniforms(env, builder, name.c_str(), values, count);
+}
+
+static void RuntimeShader_updateIntArrayUniforms(JNIEnv* env, jobject, jlong shaderBuilder,
+                                                 jstring jUniformName, jintArray jvalues) {
+    SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
+    ScopedUtfChars name(env, jUniformName);
+    AutoJavaIntArray autoValues(env, jvalues, 0);
+    UpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+}
+
 static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder,
                                            jstring jUniformName, jlong shaderHandle) {
     SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder);
@@ -338,7 +416,14 @@
         {"nativeGetFinalizer", "()J", (void*)RuntimeShader_getNativeFinalizer},
         {"nativeCreateShader", "(JJZ)J", (void*)RuntimeShader_create},
         {"nativeCreateBuilder", "(Ljava/lang/String;)J", (void*)RuntimeShader_createShaderBuilder},
-        {"nativeUpdateUniforms", "(JLjava/lang/String;[F)V", (void*)RuntimeShader_updateUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+         (void*)RuntimeShader_updateFloatArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+         (void*)RuntimeShader_updateFloatUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+         (void*)RuntimeShader_updateIntArrayUniforms},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+         (void*)RuntimeShader_updateIntUniforms},
         {"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader},
 };
 
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 9bca4df..744739a 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -91,6 +91,8 @@
         fboInfo.fFormat = GL_RGBA8;
     } else if (colorType == kRGBA_1010102_SkColorType) {
         fboInfo.fFormat = GL_RGB10_A2;
+    } else if (colorType == kAlpha_8_SkColorType) {
+        fboInfo.fFormat = GL_R8;
     } else {
         LOG_ALWAYS_FATAL("Unsupported color type.");
     }
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 4e7471d..bc386fe 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -613,6 +613,10 @@
             mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
             mSurfaceColorSpace = SkColorSpace::MakeRGB(GetPQSkTransferFunction(), SkNamedGamut::kRec2020);
             break;
+        case ColorMode::A8:
+            mSurfaceColorType = SkColorType::kAlpha_8_SkColorType;
+            mSurfaceColorSpace = nullptr;
+            break;
     }
 }
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 38b5715..bdad772 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -526,8 +526,9 @@
         if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
             const auto inputEventId =
                     static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
-            native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), vsyncId,
-                                                  inputEventId);
+            native_window_set_frame_timeline_info(
+                    mNativeSurface->getNativeWindow(), vsyncId, inputEventId,
+                    mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime));
         }
     }
 
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index c7d7a17..02257db 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -90,6 +90,7 @@
         , mEglConfig(nullptr)
         , mEglConfigF16(nullptr)
         , mEglConfig1010102(nullptr)
+        , mEglConfigA8(nullptr)
         , mEglContext(EGL_NO_CONTEXT)
         , mPBufferSurface(EGL_NO_SURFACE)
         , mCurrentSurface(EGL_NO_SURFACE)
@@ -246,6 +247,50 @@
     return config;
 }
 
+EGLConfig EglManager::loadA8Config(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
+    EGLint eglSwapBehavior =
+            (swapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+    EGLint attribs[] = {EGL_RENDERABLE_TYPE,
+                        EGL_OPENGL_ES2_BIT,
+                        EGL_RED_SIZE,
+                        8,
+                        EGL_GREEN_SIZE,
+                        0,
+                        EGL_BLUE_SIZE,
+                        0,
+                        EGL_ALPHA_SIZE,
+                        0,
+                        EGL_DEPTH_SIZE,
+                        0,
+                        EGL_SURFACE_TYPE,
+                        EGL_WINDOW_BIT | eglSwapBehavior,
+                        EGL_NONE};
+    EGLint numConfigs = 1;
+    if (!eglChooseConfig(display, attribs, nullptr, numConfigs, &numConfigs)) {
+        return EGL_NO_CONFIG_KHR;
+    }
+
+    std::vector<EGLConfig> configs(numConfigs, EGL_NO_CONFIG_KHR);
+    if (!eglChooseConfig(display, attribs, configs.data(), numConfigs, &numConfigs)) {
+        return EGL_NO_CONFIG_KHR;
+    }
+
+    // The component sizes passed to eglChooseConfig are minimums, so configs
+    // contains entries that exceed them. Choose one that matches the sizes
+    // exactly.
+    for (EGLConfig config : configs) {
+        EGLint r{0}, g{0}, b{0}, a{0};
+        eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r);
+        eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g);
+        eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b);
+        eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a);
+        if (8 == r && 0 == g && 0 == b && 0 == a) {
+            return config;
+        }
+    }
+    return EGL_NO_CONFIG_KHR;
+}
+
 void EglManager::initExtensions() {
     auto extensions = StringUtils::split(eglQueryString(mEglDisplay, EGL_EXTENSIONS));
 
@@ -345,10 +390,14 @@
                                                      sk_sp<SkColorSpace> colorSpace) {
     LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
 
-    if (!mHasWideColorGamutSupport || !EglExtensions.noConfigContext) {
+    if (!EglExtensions.noConfigContext) {
+        // The caller shouldn't use A8 if we cannot switch modes.
+        LOG_ALWAYS_FATAL_IF(colorMode == ColorMode::A8,
+                            "Cannot use A8 without EGL_KHR_no_config_context!");
+
+        // Cannot switch modes without EGL_KHR_no_config_context.
         colorMode = ColorMode::Default;
     }
-
     // The color space we want to use depends on whether linear blending is turned
     // on and whether the app has requested wide color gamut rendering. When wide
     // color gamut rendering is off, the app simply renders in the display's native
@@ -374,42 +423,61 @@
     EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE};
 
     EGLConfig config = mEglConfig;
-    if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
-        if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
-            colorMode = ColorMode::Default;
-        } else {
-            config = mEglConfigF16;
+    if (colorMode == ColorMode::A8) {
+        // A8 doesn't use a color space
+        if (!mEglConfigA8) {
+            mEglConfigA8 = loadA8Config(mEglDisplay, mSwapBehavior);
+            LOG_ALWAYS_FATAL_IF(!mEglConfigA8,
+                                "Requested ColorMode::A8, but EGL lacks support! error = %s",
+                                eglErrorString());
         }
-    }
-    if (EglExtensions.glColorSpace) {
-        attribs[0] = EGL_GL_COLORSPACE_KHR;
-        switch (colorMode) {
-            case ColorMode::Default:
-                attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
-                break;
-            case ColorMode::WideColorGamut: {
-                skcms_Matrix3x3 colorGamut;
-                LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
-                                    "Could not get gamut matrix from color space");
-                if (memcmp(&colorGamut, &SkNamedGamut::kDisplayP3, sizeof(colorGamut)) == 0) {
-                    attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
-                } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
-                    attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
-                } else if (memcmp(&colorGamut, &SkNamedGamut::kRec2020, sizeof(colorGamut)) == 0) {
-                    attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
-                } else {
-                    LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
-                }
-                break;
-            }
-            case ColorMode::Hdr:
+        config = mEglConfigA8;
+    } else {
+        if (!mHasWideColorGamutSupport) {
+            colorMode = ColorMode::Default;
+        }
+
+        if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+            if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
+                colorMode = ColorMode::Default;
+            } else {
                 config = mEglConfigF16;
-                attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
-                break;
-            case ColorMode::Hdr10:
-                config = mEglConfig1010102;
-                attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
-                break;
+            }
+        }
+        if (EglExtensions.glColorSpace) {
+            attribs[0] = EGL_GL_COLORSPACE_KHR;
+            switch (colorMode) {
+                case ColorMode::Default:
+                    attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
+                    break;
+                case ColorMode::WideColorGamut: {
+                    skcms_Matrix3x3 colorGamut;
+                    LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
+                                        "Could not get gamut matrix from color space");
+                    if (memcmp(&colorGamut, &SkNamedGamut::kDisplayP3, sizeof(colorGamut)) == 0) {
+                        attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+                    } else if (memcmp(&colorGamut, &SkNamedGamut::kSRGB, sizeof(colorGamut)) == 0) {
+                        attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+                    } else if (memcmp(&colorGamut, &SkNamedGamut::kRec2020, sizeof(colorGamut)) ==
+                               0) {
+                        attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+                    } else {
+                        LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
+                    }
+                    break;
+                }
+                case ColorMode::Hdr:
+                    config = mEglConfigF16;
+                    attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+                    break;
+                case ColorMode::Hdr10:
+                    config = mEglConfig1010102;
+                    attribs[1] = EGL_GL_COLORSPACE_BT2020_PQ_EXT;
+                    break;
+                case ColorMode::A8:
+                    LOG_ALWAYS_FATAL("Unreachable: A8 doesn't use a color space");
+                    break;
+            }
         }
     }
 
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 69f3ed0..fc6b28d 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -89,6 +89,7 @@
     static EGLConfig load8BitsConfig(EGLDisplay display, SwapBehavior swapBehavior);
     static EGLConfig loadFP16Config(EGLDisplay display, SwapBehavior swapBehavior);
     static EGLConfig load1010102Config(EGLDisplay display, SwapBehavior swapBehavior);
+    static EGLConfig loadA8Config(EGLDisplay display, SwapBehavior swapBehavior);
 
     void initExtensions();
     void createPBufferSurface();
@@ -100,6 +101,7 @@
     EGLConfig mEglConfig;
     EGLConfig mEglConfigF16;
     EGLConfig mEglConfig1010102;
+    EGLConfig mEglConfigA8;
     EGLContext mEglContext;
     EGLSurface mPBufferSurface;
     EGLSurface mCurrentSurface;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 9e8a1e1..a9ff2c6 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -35,6 +35,9 @@
 #include "pipeline/skia/ShaderCache.h"
 #include "renderstate/RenderState.h"
 
+#undef LOG_TAG
+#define LOG_TAG "VulkanManager"
+
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 611a4d9..7dd3561 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -24,6 +24,9 @@
 #include "VulkanManager.h"
 #include "utils/Color.h"
 
+#undef LOG_TAG
+#define LOG_TAG "VulkanSurface"
+
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -197,8 +200,9 @@
     outWindowInfo->bufferFormat = ColorTypeToBufferFormat(colorType);
     outWindowInfo->colorspace = colorSpace;
     outWindowInfo->dataspace = ColorSpaceToADataSpace(colorSpace.get(), colorType);
-    LOG_ALWAYS_FATAL_IF(outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN,
-                        "Unsupported colorspace");
+    LOG_ALWAYS_FATAL_IF(
+            outWindowInfo->dataspace == HAL_DATASPACE_UNKNOWN && colorType != kAlpha_8_SkColorType,
+            "Unsupported colorspace");
 
     VkFormat vkPixelFormat;
     switch (colorType) {
@@ -211,6 +215,9 @@
         case kRGBA_1010102_SkColorType:
             vkPixelFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
             break;
+        case kAlpha_8_SkColorType:
+            vkPixelFormat = VK_FORMAT_R8_UNORM;
+            break;
         default:
             LOG_ALWAYS_FATAL("Unsupported colorType: %d", (int)colorType);
     }
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index 9f3be16..2293ace 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -57,6 +57,10 @@
             colorType = kRGBA_F16_SkColorType;
             alphaType = kPremul_SkAlphaType;
             break;
+        case AHARDWAREBUFFER_FORMAT_R8_UNORM:
+            colorType = kAlpha_8_SkColorType;
+            alphaType = kPremul_SkAlphaType;
+            break;
         default:
             ALOGV("Unsupported format: %d, return unknown by default", format);
             break;
@@ -90,6 +94,8 @@
             // Hardcoding the value from android::PixelFormat
             static constexpr uint64_t kRGBA4444 = 7;
             return kRGBA4444;
+        case kAlpha_8_SkColorType:
+              return AHARDWAREBUFFER_FORMAT_R8_UNORM;
         default:
             ALOGV("Unsupported colorType: %d, return RGBA_8888 by default", (int)colorType);
             return AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
diff --git a/location/java/android/location/GnssMeasurement.java b/location/java/android/location/GnssMeasurement.java
index ecdd4b6..2c94820d 100644
--- a/location/java/android/location/GnssMeasurement.java
+++ b/location/java/android/location/GnssMeasurement.java
@@ -1860,7 +1860,7 @@
             gnssMeasurement.mSatelliteInterSignalBiasUncertaintyNanos = parcel.readDouble();
             if (gnssMeasurement.hasSatellitePvt()) {
                 ClassLoader classLoader = getClass().getClassLoader();
-                gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader);
+                gnssMeasurement.mSatellitePvt = parcel.readParcelable(classLoader, android.location.SatellitePvt.class);
             }
             if (gnssMeasurement.hasCorrelationVectors()) {
                 CorrelationVector[] correlationVectorsArray =
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index a07a64a..b744017 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -156,7 +156,7 @@
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
             ClassLoader classLoader = getClass().getClassLoader();
 
-            GnssClock clock = in.readParcelable(classLoader);
+            GnssClock clock = in.readParcelable(classLoader, android.location.GnssClock.class);
 
             int measurementsLength = in.readInt();
             GnssMeasurement[] measurementsArray = new GnssMeasurement[measurementsLength];
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
index f3feb7a..6b834f3 100644
--- a/location/java/android/location/GpsMeasurementsEvent.java
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -112,7 +112,7 @@
         public GpsMeasurementsEvent createFromParcel(Parcel in) {
             ClassLoader classLoader = getClass().getClassLoader();
 
-            GpsClock clock = in.readParcelable(classLoader);
+            GpsClock clock = in.readParcelable(classLoader, android.location.GpsClock.class);
 
             int measurementsLength = in.readInt();
             GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength];
diff --git a/location/java/android/location/GpsNavigationMessageEvent.java b/location/java/android/location/GpsNavigationMessageEvent.java
index 2d5d6eb..b37fe3d 100644
--- a/location/java/android/location/GpsNavigationMessageEvent.java
+++ b/location/java/android/location/GpsNavigationMessageEvent.java
@@ -92,7 +92,7 @@
                 @Override
                 public GpsNavigationMessageEvent createFromParcel(Parcel in) {
                     ClassLoader classLoader = getClass().getClassLoader();
-                    GpsNavigationMessage navigationMessage = in.readParcelable(classLoader);
+                    GpsNavigationMessage navigationMessage = in.readParcelable(classLoader, android.location.GpsNavigationMessage.class);
                     return new GpsNavigationMessageEvent(navigationMessage);
                 }
 
diff --git a/location/java/android/location/SatellitePvt.java b/location/java/android/location/SatellitePvt.java
index 794a8d0..aa43cfd 100644
--- a/location/java/android/location/SatellitePvt.java
+++ b/location/java/android/location/SatellitePvt.java
@@ -465,9 +465,9 @@
                 public SatellitePvt createFromParcel(Parcel in) {
                     int flags = in.readInt();
                     ClassLoader classLoader = getClass().getClassLoader();
-                    PositionEcef positionEcef = in.readParcelable(classLoader);
-                    VelocityEcef velocityEcef = in.readParcelable(classLoader);
-                    ClockInfo clockInfo = in.readParcelable(classLoader);
+                    PositionEcef positionEcef = in.readParcelable(classLoader, android.location.SatellitePvt.PositionEcef.class);
+                    VelocityEcef velocityEcef = in.readParcelable(classLoader, android.location.SatellitePvt.VelocityEcef.class);
+                    ClockInfo clockInfo = in.readParcelable(classLoader, android.location.SatellitePvt.ClockInfo.class);
                     double ionoDelayMeters = in.readDouble();
                     double tropoDelayMeters = in.readDouble();
 
diff --git a/media/aidl/android/media/audio/common/AudioPort.aidl b/media/aidl/android/media/audio/common/AudioPort.aidl
index 8e1c5af..84675e3 100644
--- a/media/aidl/android/media/audio/common/AudioPort.aidl
+++ b/media/aidl/android/media/audio/common/AudioPort.aidl
@@ -18,7 +18,6 @@
 
 import android.media.audio.common.AudioGain;
 import android.media.audio.common.AudioIoFlags;
-import android.media.audio.common.AudioPortConfig;
 import android.media.audio.common.AudioPortExt;
 import android.media.audio.common.AudioProfile;
 import android.media.audio.common.ExtraAudioDescriptor;
@@ -33,7 +32,7 @@
 @VintfStability
 parcelable AudioPort {
     /**
-     * Unique identifier of the port within this HAL service.
+     * Unique identifier of the port within a HAL module.
      */
     int id;
     /**
@@ -57,8 +56,6 @@
     ExtraAudioDescriptor[] extraAudioDescriptors;
     /** Gain controllers. */
     AudioGain[] gains;
-    /** Current audio port configuration. */
-    AudioPortConfig activeConfig;
     /** Extra parameters depending on the port role. */
     AudioPortExt ext;
 }
diff --git a/media/aidl/android/media/audio/common/AudioPortConfig.aidl b/media/aidl/android/media/audio/common/AudioPortConfig.aidl
index e3a9374..2702b14 100644
--- a/media/aidl/android/media/audio/common/AudioPortConfig.aidl
+++ b/media/aidl/android/media/audio/common/AudioPortConfig.aidl
@@ -33,10 +33,15 @@
 @VintfStability
 parcelable AudioPortConfig {
     /**
-     * Port unique ID. This field is set to a non-zero value when it is needed
-     * to select a previously reported port and apply new configuration to it.
+     * Port config unique ID. This field is set to a non-zero value when it is
+     * needed to select a previously reported port config and apply new
+     * configuration to it.
      */
     int id;
+    /**
+     * The ID of the AudioPort instance this configuration applies to.
+     */
+    int portId;
     /** Sample rate in Hz. Can be left unspecified. */
     @nullable Int sampleRate;
     /** Channel mask. Can be left unspecified. */
diff --git a/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl b/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl
index 940566c..63136d7 100644
--- a/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl
+++ b/media/aidl/android/media/audio/common/AudioPortDeviceExt.aidl
@@ -29,10 +29,23 @@
 parcelable AudioPortDeviceExt {
     /** Audio device specification. */
     AudioDevice device;
+    /** Bitmask indexed by 'FLAG_INDEX_' constants. */
+    int flags;
     /**
      * List of supported encoded formats. Specified for ports that perform
      * hardware-accelerated decoding or transcoding, or connected to external
      * hardware.
      */
     AudioFormatDescription[] encodedFormats;
+
+    /**
+     * A default device port is fallback used when the preference for the device
+     * to use has not been specified (AudioDeviceType.type == {IN|OUT}_DEFAULT),
+     * or the specified device does not satisfy routing criteria based on audio
+     * stream attributes and use cases. The device port for which the ID is
+     * returned must be associated with a permanently attached device
+     * (AudioDeviceDescription.connection == ''). There can be no more than one
+     * default device port in a HAL module in each I/O direction.
+     */
+    const int FLAG_INDEX_DEFAULT_DEVICE = 0;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl
index 8f563b3..970bbc0 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl
@@ -41,6 +41,5 @@
   android.media.audio.common.AudioIoFlags flags;
   android.media.audio.common.ExtraAudioDescriptor[] extraAudioDescriptors;
   android.media.audio.common.AudioGain[] gains;
-  android.media.audio.common.AudioPortConfig activeConfig;
   android.media.audio.common.AudioPortExt ext;
 }
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl
index 78967b4..18e6406 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl
@@ -36,6 +36,7 @@
 @JavaDerive(equals=true, toString=true) @VintfStability
 parcelable AudioPortConfig {
   int id;
+  int portId;
   @nullable android.media.audio.common.Int sampleRate;
   @nullable android.media.audio.common.AudioChannelLayout channelMask;
   @nullable android.media.audio.common.AudioFormatDescription format;
diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl
index 2e8124a..37d7041 100644
--- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl
+++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortDeviceExt.aidl
@@ -36,5 +36,7 @@
 @JavaDerive(equals=true, toString=true) @VintfStability
 parcelable AudioPortDeviceExt {
   android.media.audio.common.AudioDevice device;
+  int flags;
   android.media.audio.common.AudioFormatDescription[] encodedFormats;
+  const int FLAG_INDEX_DEFAULT_DEVICE = 0;
 }
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 54252b5..9993ce9 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -481,13 +481,20 @@
      */
     public static final int FLAG_NEVER_SPATIALIZE = 0x1 << 15;
 
+    /**
+     * @hide
+     * Flag indicating the audio is part of a call redirection.
+     * Valid for playback and capture.
+     */
+    public static final int FLAG_CALL_REDIRECTION = 0x1 << 16;
+
     // Note that even though FLAG_MUTE_HAPTIC is stored as a flag bit, it is not here since
     // it is known as a boolean value outside of AudioAttributes.
     private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO
             | FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY
             | FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION
             | FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE | FLAG_CONTENT_SPATIALIZED
-            | FLAG_NEVER_SPATIALIZE;
+            | FLAG_NEVER_SPATIALIZE | FLAG_CALL_REDIRECTION;
     private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
             FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
     /* mask of flags that can be set by SDK and System APIs through the Builder */
@@ -707,6 +714,14 @@
         return ALLOW_CAPTURE_BY_ALL;
     }
 
+    /**
+     * @hide
+     * Indicates if the audio is used for call redirection
+     * @return true if used for call redirection, false otherwise.
+     */
+    public boolean isForCallRedirection() {
+        return (mFlags & FLAG_CALL_REDIRECTION) == FLAG_CALL_REDIRECTION;
+    }
 
     /**
      * Builder class for {@link AudioAttributes} objects.
@@ -763,11 +778,15 @@
         public Builder(AudioAttributes aa) {
             mUsage = aa.mUsage;
             mContentType = aa.mContentType;
+            mSource = aa.mSource;
             mFlags = aa.getAllFlags();
             mTags = (HashSet<String>) aa.mTags.clone();
             mMuteHapticChannels = aa.areHapticChannelsMuted();
             mIsContentSpatialized = aa.isContentSpatialized();
             mSpatializationBehavior = aa.getSpatializationBehavior();
+            if ((mFlags & FLAG_CAPTURE_PRIVATE) != 0) {
+                mPrivacySensitive = PRIVACY_SENSITIVE_ENABLED;
+            }
         }
 
         /**
@@ -1071,6 +1090,17 @@
         }
 
         /**
+         * @hide
+         * Replace all custom tags
+         * @param tags
+         * @return the same Builder instance.
+         */
+        public Builder replaceTags(HashSet<String> tags) {
+            mTags = (HashSet<String>) tags.clone();
+            return this;
+        }
+
+        /**
          * Sets attributes as inferred from the legacy stream types.
          * Warning: do not use this method in combination with setting any other attributes such as
          * usage, content type, flags or haptic control, as this method will overwrite (the more
@@ -1245,6 +1275,16 @@
                 privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
             return this;
         }
+
+        /**
+         * @hide
+         * Designates the audio to be used for call redirection
+         * @return the same Builder instance.
+         */
+        public Builder setForCallRedirection() {
+            mFlags |= FLAG_CALL_REDIRECTION;
+            return this;
+        }
     };
 
     @Override
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index 7caac89..1448c49 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -110,7 +110,7 @@
             mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(type);
         } else if (role == ROLE_INPUT) {
             AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
-            mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(type);
+            mNativeType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(type, address);
         } else {
             mNativeType = AudioSystem.DEVICE_NONE;
         }
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index a186566..211a50e 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -581,7 +581,16 @@
 
     /** @hide */
     public static int convertDeviceTypeToInternalInputDevice(int deviceType) {
-        return EXT_TO_INT_INPUT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE);
+        return convertDeviceTypeToInternalInputDevice(deviceType, "");
+    }
+    /** @hide */
+    public static int convertDeviceTypeToInternalInputDevice(int deviceType, String address) {
+        int internalType = EXT_TO_INT_INPUT_DEVICE_MAPPING.get(deviceType, AudioSystem.DEVICE_NONE);
+        if (internalType == AudioSystem.DEVICE_IN_BUILTIN_MIC
+                && "back".equals(address)) {
+            internalType = AudioSystem.DEVICE_IN_BACK_MIC;
+        }
+        return internalType;
     }
 
     private static final SparseIntArray INT_TO_EXT_DEVICE_MAPPING;
@@ -671,9 +680,6 @@
         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_FM, AudioSystem.DEVICE_OUT_FM);
-        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_MIC, AudioSystem.DEVICE_IN_BUILTIN_MIC);
-        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_FM_TUNER, AudioSystem.DEVICE_IN_FM_TUNER);
-        EXT_TO_INT_DEVICE_MAPPING.put(TYPE_TV_TUNER, AudioSystem.DEVICE_IN_TV_TUNER);
         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);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index faae9a5..0722417 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -88,7 +88,8 @@
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
-
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * AudioManager provides access to volume and ringer mode control.
@@ -4002,8 +4003,7 @@
      * @hide
      * flag set on test API calls,
      * see {@link #requestAudioFocusForTest(AudioFocusRequest, String, int, int)},
-     * note that it isn't used in conjunction with other flags, it is passed as the single
-     * value for flags */
+     */
     public static final int AUDIOFOCUS_FLAG_TEST = 0x1 << 3;
     /** @hide */
     public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK
@@ -4186,7 +4186,9 @@
                     afr.getFocusGain(),
                     mICallBack,
                     mAudioFocusDispatcher,
-                    clientFakeId, "com.android.test.fakeclient", clientFakeUid, clientTargetSdk);
+                    clientFakeId, "com.android.test.fakeclient",
+                    afr.getFlags() | AudioManager.AUDIOFOCUS_FLAG_TEST,
+                    clientFakeUid, clientTargetSdk);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -5775,6 +5777,23 @@
     }
 
     /**
+     * Indicate wired accessory connection state change.
+     * @param device {@link AudioDeviceAttributes} of the device to "fake-connect"
+     * @param connected true for connected, false for disconnected
+     * {@hide}
+     */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+            boolean connected) {
+        try {
+            getService().setTestDeviceConnectionState(device, connected);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Indicate Bluetooth profile connection state change.
      * Configuration changes for A2DP are indicated by having the same <code>newDevice</code> and
      * <code>previousDevice</code>
@@ -7570,7 +7589,7 @@
         return getDeviceInfoFromTypeAndAddress(deviceType, null);
     }
 
-        /**
+    /**
      * @hide
      * Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type and
      * address provided.
@@ -7692,6 +7711,506 @@
         }
     }
 
+
+    /**
+     * @hide
+     * Indicates if the platform allows accessing the uplink and downlink audio of an ongoing
+     * PSTN call.
+     * When true, {@link getCallUplinkInjectionAudioTrack(AudioFormat)} can be used to obtain
+     * an AudioTrack for call uplink audio injection and
+     * {@link getCallDownlinkExtractionAudioRecord(AudioFormat)} can be used to obtain
+     * an AudioRecord for call downlink audio extraction.
+     * @return true if PSTN call audio is accessible, false otherwise.
+     */
+    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+    public boolean isPstnCallAudioInterceptable() {
+        final IAudioService service = getService();
+        try {
+            return service.isPstnCallAudioInterceptable();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @IntDef(flag = false, prefix = "CALL_REDIRECT_", value = {
+            CALL_REDIRECT_NONE,
+            CALL_REDIRECT_PSTN,
+            CALL_REDIRECT_VOIP }
+            )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CallRedirectionMode {}
+
+    /**
+     * Not used for call redirection
+     * @hide
+     */
+    public static final int CALL_REDIRECT_NONE = 0;
+    /**
+     * Used to redirect  PSTN call
+     * @hide
+     */
+    public static final int CALL_REDIRECT_PSTN = 1;
+    /**
+     * Used to redirect  VoIP call
+     * @hide
+     */
+    public static final int CALL_REDIRECT_VOIP = 2;
+
+
+    private @CallRedirectionMode int getCallRedirectMode() {
+        int mode = getMode();
+        if (mode == MODE_IN_CALL || mode == MODE_CALL_SCREENING
+                || mode == MODE_CALL_REDIRECT) {
+            return CALL_REDIRECT_PSTN;
+        } else if (mode == MODE_IN_COMMUNICATION || mode == MODE_COMMUNICATION_REDIRECT) {
+            return CALL_REDIRECT_VOIP;
+        }
+        return CALL_REDIRECT_NONE;
+    }
+
+    private void checkCallRedirectionFormat(AudioFormat format, boolean isOutput) {
+        if (format.getEncoding() != AudioFormat.ENCODING_PCM_16BIT
+                && format.getEncoding() != AudioFormat.ENCODING_PCM_FLOAT) {
+            throw new UnsupportedOperationException(" Unsupported encoding ");
+        }
+        if (format.getSampleRate() < 8000
+                || format.getSampleRate() > 48000) {
+            throw new UnsupportedOperationException(" Unsupported sample rate ");
+        }
+        if (isOutput && format.getChannelMask() != AudioFormat.CHANNEL_OUT_MONO
+                && format.getChannelMask() != AudioFormat.CHANNEL_OUT_STEREO) {
+            throw new UnsupportedOperationException(" Unsupported output channel mask ");
+        }
+        if (!isOutput && format.getChannelMask() != AudioFormat.CHANNEL_IN_MONO
+                && format.getChannelMask() != AudioFormat.CHANNEL_IN_STEREO) {
+            throw new UnsupportedOperationException(" Unsupported input channel mask ");
+        }
+    }
+
+    class CallIRedirectionClientInfo {
+        public WeakReference trackOrRecord;
+        public int redirectMode;
+    }
+
+    private Object mCallRedirectionLock = new Object();
+    @GuardedBy("mCallRedirectionLock")
+    private CallInjectionModeChangedListener mCallRedirectionModeListener;
+    @GuardedBy("mCallRedirectionLock")
+    private ArrayList<CallIRedirectionClientInfo> mCallIRedirectionClients;
+
+    /**
+     * @hide
+     * Returns an AudioTrack that can be used to inject audio to an active call uplink.
+     * This can be used for functions like call screening or call audio redirection and is reserved
+     * to system apps with privileged permission.
+     * @param format the desired audio format for audio playback.
+     * p>Formats accepted are:
+     * <ul>
+     *   <li><em>Sampling rate</em> - 8kHz to 48kHz. </li>
+     *   <li><em>Channel mask</em> - Mono or Stereo </li>
+     *   <li><em>Sample format</em> - PCM 16 bit or FLOAT 32 bit </li>
+     * </ul>
+     *
+     * @return The AudioTrack used for audio injection
+     * @throws NullPointerException if AudioFormat argument is null.
+     * @throws UnsupportedOperationException if on unsupported AudioFormat is specified.
+     * @throws IllegalArgumentException if an invalid AudioFormat is specified.
+     * @throws SecurityException if permission CALL_AUDIO_INTERCEPTION  is missing .
+     * @throws IllegalStateException if current audio mode is not MODE_IN_CALL,
+     *         MODE_IN_COMMUNICATION, MODE_CALL_SCREENING, MODE_CALL_REDIRECT
+     *         or MODE_COMMUNICATION_REDIRECT.
+     */
+    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+    public @NonNull AudioTrack getCallUplinkInjectionAudioTrack(@NonNull AudioFormat format) {
+        Objects.requireNonNull(format);
+        checkCallRedirectionFormat(format, true /* isOutput */);
+
+        AudioTrack track = null;
+        int redirectMode = getCallRedirectMode();
+        if (redirectMode == CALL_REDIRECT_NONE) {
+            throw new IllegalStateException(
+                    " not available in mode " + AudioSystem.modeToString(getMode()));
+        } else if (redirectMode == CALL_REDIRECT_PSTN && !isPstnCallAudioInterceptable()) {
+            throw new UnsupportedOperationException(" PSTN Call audio not accessible ");
+        }
+
+        track = new AudioTrack.Builder()
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setSystemUsage(AudioAttributes.USAGE_CALL_ASSISTANT)
+                        .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+                        .build())
+                .setAudioFormat(format)
+                .setCallRedirectionMode(redirectMode)
+                .build();
+
+        if (track != null && track.getState() != AudioTrack.STATE_UNINITIALIZED) {
+            synchronized (mCallRedirectionLock) {
+                if (mCallRedirectionModeListener == null) {
+                    mCallRedirectionModeListener = new CallInjectionModeChangedListener();
+                    try {
+                        addOnModeChangedListener(
+                                Executors.newSingleThreadExecutor(), mCallRedirectionModeListener);
+                    } catch (Exception e) {
+                        Log.e(TAG, "addOnModeChangedListener failed with exception: " + e);
+                        mCallRedirectionModeListener = null;
+                        throw new UnsupportedOperationException(" Cannot register mode listener ");
+                    }
+                    mCallIRedirectionClients = new ArrayList<CallIRedirectionClientInfo>();
+                }
+                CallIRedirectionClientInfo info = new CallIRedirectionClientInfo();
+                info.redirectMode = redirectMode;
+                info.trackOrRecord = new WeakReference<AudioTrack>(track);
+                mCallIRedirectionClients.add(info);
+            }
+        } else {
+            throw new UnsupportedOperationException(" Cannot create the AudioTrack");
+        }
+        return track;
+    }
+
+    /**
+     * @hide
+     * Returns an AudioRecord that can be used to extract audio from an active call downlink.
+     * This can be used for functions like call screening or call audio redirection and is reserved
+     * to system apps with privileged permission.
+     * @param format the desired audio format for audio capture.
+     *<p>Formats accepted are:
+     * <ul>
+     *   <li><em>Sampling rate</em> - 8kHz to 48kHz. </li>
+     *   <li><em>Channel mask</em> - Mono or Stereo </li>
+     *   <li><em>Sample format</em> - PCM 16 bit or FLOAT 32 bit </li>
+     * </ul>
+     *
+     * @return The AudioRecord used for audio extraction
+     * @throws UnsupportedOperationException if on unsupported AudioFormat is specified.
+     * @throws IllegalArgumentException if an invalid AudioFormat is specified.
+     * @throws NullPointerException if AudioFormat argument is null.
+     * @throws SecurityException if permission CALL_AUDIO_INTERCEPTION  is missing .
+     * @throws IllegalStateException if current audio mode is not MODE_IN_CALL,
+     *         MODE_IN_COMMUNICATION, MODE_CALL_SCREENING, MODE_CALL_REDIRECT
+     *         or MODE_COMMUNICATION_REDIRECT.
+     */
+    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+    public @NonNull AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull AudioFormat format) {
+        Objects.requireNonNull(format);
+        checkCallRedirectionFormat(format, false /* isOutput */);
+
+        AudioRecord record = null;
+        int redirectMode = getCallRedirectMode();
+        if (redirectMode == CALL_REDIRECT_NONE) {
+            throw new IllegalStateException(
+                    " not available in mode " + AudioSystem.modeToString(getMode()));
+        } else if (redirectMode == CALL_REDIRECT_PSTN && !isPstnCallAudioInterceptable()) {
+            throw new UnsupportedOperationException(" PSTN Call audio not accessible ");
+        }
+
+        record = new AudioRecord.Builder()
+                .setAudioAttributes(new AudioAttributes.Builder()
+                        .setInternalCapturePreset(MediaRecorder.AudioSource.VOICE_DOWNLINK)
+                        .build())
+                .setAudioFormat(format)
+                .setCallRedirectionMode(redirectMode)
+                .build();
+
+        if (record != null && record.getState() != AudioRecord.STATE_UNINITIALIZED) {
+            synchronized (mCallRedirectionLock) {
+                if (mCallRedirectionModeListener == null) {
+                    mCallRedirectionModeListener = new CallInjectionModeChangedListener();
+                    try {
+                        addOnModeChangedListener(
+                                Executors.newSingleThreadExecutor(), mCallRedirectionModeListener);
+                    } catch (Exception e) {
+                        Log.e(TAG, "addOnModeChangedListener failed with exception: " + e);
+                        mCallRedirectionModeListener = null;
+                        throw new UnsupportedOperationException(" Cannot register mode listener ");
+                    }
+                    mCallIRedirectionClients = new ArrayList<CallIRedirectionClientInfo>();
+                }
+                CallIRedirectionClientInfo info = new CallIRedirectionClientInfo();
+                info.redirectMode = redirectMode;
+                info.trackOrRecord = new WeakReference<AudioRecord>(record);
+                mCallIRedirectionClients.add(info);
+            }
+        } else {
+            throw new UnsupportedOperationException(" Cannot create the AudioRecord");
+        }
+        return record;
+    }
+
+    class CallInjectionModeChangedListener implements OnModeChangedListener {
+        @Override
+        public void onModeChanged(@AudioMode int mode) {
+            synchronized (mCallRedirectionLock) {
+                final ArrayList<CallIRedirectionClientInfo> clientInfos =
+                        (ArrayList<CallIRedirectionClientInfo>) mCallIRedirectionClients.clone();
+                for (CallIRedirectionClientInfo info : clientInfos) {
+                    Object trackOrRecord = info.trackOrRecord.get();
+                    if (trackOrRecord != null) {
+                        if ((info.redirectMode ==  CALL_REDIRECT_PSTN
+                                && mode != MODE_IN_CALL && mode != MODE_CALL_SCREENING
+                                && mode != MODE_CALL_REDIRECT)
+                                || (info.redirectMode == CALL_REDIRECT_VOIP
+                                    && mode != MODE_IN_COMMUNICATION
+                                    && mode != MODE_COMMUNICATION_REDIRECT)) {
+                            if (trackOrRecord instanceof AudioTrack) {
+                                AudioTrack track = (AudioTrack) trackOrRecord;
+                                track.release();
+                            } else {
+                                AudioRecord record = (AudioRecord) trackOrRecord;
+                                record.release();
+                            }
+                            mCallIRedirectionClients.remove(info);
+                        }
+                    }
+                }
+                if (mCallIRedirectionClients.isEmpty()) {
+                    try {
+                        if (mCallRedirectionModeListener != null) {
+                            removeOnModeChangedListener(mCallRedirectionModeListener);
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "removeOnModeChangedListener failed with exception: " + e);
+                    } finally {
+                        mCallRedirectionModeListener = null;
+                        mCallIRedirectionClients = null;
+                    }
+                }
+            }
+        }
+    }
+
+    //---------------------------------------------------------
+    // audio device connection-dependent muting
+    /**
+     * @hide
+     * Mute a set of playback use cases until a given audio device is connected.
+     * Automatically unmute upon connection of the device, or after the given timeout, whichever
+     * happens first.
+     * @param usagesToMute non-empty array of {@link AudioAttributes} usages (for example
+     *                     {@link AudioAttributes#USAGE_MEDIA}) to mute until the
+     *                     device connects
+     * @param device the audio device expected to connect within the timeout duration
+     * @param timeout the maximum amount of time to wait for the device connection
+     * @param timeUnit the unit for the timeout
+     * @throws IllegalStateException when trying to issue the command while another is already in
+     *         progress and hasn't been cancelled by
+     *         {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}. See
+     *         {@link #getMutingExpectedDevice()} to check if a muting command is active.
+     * @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void muteAwaitConnection(@NonNull int[] usagesToMute,
+            @NonNull AudioDeviceAttributes device,
+            long timeout, @NonNull TimeUnit timeUnit) throws IllegalStateException {
+        if (timeout <= 0) {
+            throw new IllegalArgumentException("Timeout must be greater than 0");
+        }
+        Objects.requireNonNull(usagesToMute);
+        if (usagesToMute.length == 0) {
+            throw new IllegalArgumentException("Array of usages to mute cannot be empty");
+        }
+        Objects.requireNonNull(device);
+        Objects.requireNonNull(timeUnit);
+        try {
+            getService().muteAwaitConnection(usagesToMute, device, timeUnit.toMillis(timeout));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Query which audio device, if any, is causing some playback use cases to be muted until it
+     * connects.
+     * @return the audio device used in
+     *        {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}, or null
+     *        if there is no active muting command (either because the muting command was not issued
+     *        or because it timed out)
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+        try {
+            return getService().getMutingExpectedDevice();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Cancel a {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
+     * command.
+     * @param device the device whose connection was expected when the {@code muteAwaitConnection}
+     *               command was issued.
+     * @throws IllegalStateException when trying to issue the command for a device whose connection
+     *         is not anticipated by a previous call to
+     *         {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device)
+            throws IllegalStateException {
+        Objects.requireNonNull(device);
+        try {
+            getService().cancelMuteAwaitConnection(device);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * A callback class to receive events about the muting and unmuting of playback use cases
+     * conditional on the upcoming connection of an audio device.
+     * @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
+     */
+    @SystemApi
+    public abstract static class MuteAwaitConnectionCallback {
+
+        /**
+         * An event where the expected audio device connected
+         * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+         */
+        public static final int EVENT_CONNECTION = 1;
+        /**
+         * An event where the expected audio device failed connect before the timeout happened
+         * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+         */
+        public static final int EVENT_TIMEOUT    = 2;
+        /**
+         * An event where the {@code muteAwaitConnection()} command
+         * was cancelled with {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}
+         * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+         */
+        public static final int EVENT_CANCEL     = 3;
+
+        /** @hide */
+        @IntDef(flag = false, prefix = "EVENT_", value = {
+                EVENT_CONNECTION,
+                EVENT_TIMEOUT,
+                EVENT_CANCEL }
+        )
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface UnmuteEvent {}
+
+        /**
+         * Called when a number of playback use cases are muted in response to a call to
+         * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}.
+         * @param device the audio device whose connection is expected. Playback use cases are
+         *               unmuted when that device connects
+         * @param mutedUsages an array of {@link AudioAttributes} usages that describe the affected
+         *                    playback use cases.
+         */
+        public void onMutedUntilConnection(
+                @NonNull AudioDeviceAttributes device,
+                @NonNull int[] mutedUsages) {}
+
+        /**
+         * Called when an event occurred that caused playback uses cases to be unmuted
+         * @param unmuteEvent the nature of the event
+         * @param device the device that was expected to connect
+         * @param mutedUsages the array of {@link AudioAttributes} usages that were muted until
+         *                    the event occurred
+         */
+        public void onUnmutedEvent(
+                @UnmuteEvent int unmuteEvent,
+                @NonNull AudioDeviceAttributes device, @NonNull int[] mutedUsages) {}
+    }
+
+
+    /**
+     * @hide
+     * Register a callback to receive updates on the playback muting conditional on a specific
+     * audio device connection.
+     * @param executor the {@link Executor} handling the callback
+     * @param callback the callback to register
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void registerMuteAwaitConnectionCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull MuteAwaitConnectionCallback callback) {
+        synchronized (mMuteAwaitConnectionListenerLock) {
+            final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
+                    MuteAwaitConnectionDispatcherStub> res =
+                    CallbackUtil.addListener("registerMuteAwaitConnectionCallback",
+                            executor, callback, mMuteAwaitConnectionListeners,
+                            mMuteAwaitConnDispatcherStub,
+                            () -> new MuteAwaitConnectionDispatcherStub(),
+                            stub -> stub.register(true));
+            mMuteAwaitConnectionListeners = res.first;
+            mMuteAwaitConnDispatcherStub = res.second;
+        }
+    }
+
+    /**
+     * @hide
+     * Unregister a previously registered callback for playback muting conditional on device
+     * connection.
+     * @param callback the callback to unregister
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void unregisterMuteAwaitConnectionCallback(
+            @NonNull MuteAwaitConnectionCallback callback) {
+        synchronized (mMuteAwaitConnectionListenerLock) {
+            final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
+                    MuteAwaitConnectionDispatcherStub> res =
+                    CallbackUtil.removeListener("unregisterMuteAwaitConnectionCallback",
+                            callback, mMuteAwaitConnectionListeners, mMuteAwaitConnDispatcherStub,
+                            stub -> stub.register(false));
+            mMuteAwaitConnectionListeners = res.first;
+            mMuteAwaitConnDispatcherStub = res.second;
+        }
+    }
+
+    private final Object mMuteAwaitConnectionListenerLock = new Object();
+
+    @GuardedBy("mMuteAwaitConnectionListenerLock")
+    private @Nullable ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>
+            mMuteAwaitConnectionListeners;
+
+    @GuardedBy("mMuteAwaitConnectionListenerLock")
+    private MuteAwaitConnectionDispatcherStub mMuteAwaitConnDispatcherStub;
+
+    private final class MuteAwaitConnectionDispatcherStub
+            extends IMuteAwaitConnectionCallback.Stub {
+        public void register(boolean register) {
+            try {
+                getService().registerMuteAwaitConnectionDispatcher(this, register);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        @SuppressLint("GuardedBy") // lock applied inside callListeners method
+        public void dispatchOnMutedUntilConnection(AudioDeviceAttributes device,
+                int[] mutedUsages) {
+            CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
+                    mMuteAwaitConnectionListenerLock,
+                    (listener) -> listener.onMutedUntilConnection(device, mutedUsages));
+        }
+
+        @Override
+        @SuppressLint("GuardedBy") // lock applied inside callListeners method
+        public void dispatchOnUnmutedEvent(int event, AudioDeviceAttributes device,
+                int[] mutedUsages) {
+            CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
+                    mMuteAwaitConnectionListenerLock,
+                    (listener) -> listener.onUnmutedEvent(event, device, mutedUsages));
+        }
+    }
+
     //---------------------------------------------------------
     // Inner classes
     //--------------------
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 7c6ae28..e76bb42 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.media.MediaRecorder.Source;
 import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.metrics.LogSessionId;
 import android.media.projection.MediaProjection;
@@ -58,6 +59,7 @@
 import java.lang.ref.WeakReference;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
@@ -404,7 +406,9 @@
 
         // is this AudioRecord using REMOTE_SUBMIX at full volume?
         if (attributes.getCapturePreset() == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
-            final AudioAttributes.Builder filteredAttr = new AudioAttributes.Builder();
+            final AudioAttributes.Builder ab =
+                    new AudioAttributes.Builder(attributes);
+            HashSet<String> filteredTags = new HashSet<String>();
             final Iterator<String> tagsIter = attributes.getTags().iterator();
             while (tagsIter.hasNext()) {
                 final String tag = tagsIter.next();
@@ -412,15 +416,15 @@
                     mIsSubmixFullVolume = true;
                     Log.v(TAG, "Will record from REMOTE_SUBMIX at full fixed volume");
                 } else { // SUBMIX_FIXED_VOLUME: is not to be propagated to the native layers
-                    filteredAttr.addTag(tag);
+                    filteredTags.add(tag);
                 }
             }
-            filteredAttr.setInternalCapturePreset(attributes.getCapturePreset());
-            mAudioAttributes = filteredAttr.build();
-        } else {
-            mAudioAttributes = attributes;
+            ab.replaceTags(filteredTags);
+            attributes = ab.build();
         }
 
+        mAudioAttributes = attributes;
+
         int rate = format.getSampleRate();
         if (rate == AudioFormat.SAMPLE_RATE_UNSPECIFIED) {
             rate = 0;
@@ -432,7 +436,7 @@
             encoding = format.getEncoding();
         }
 
-        audioParamCheck(attributes.getCapturePreset(), rate, encoding);
+        audioParamCheck(mAudioAttributes.getCapturePreset(), rate, encoding);
 
         if ((format.getPropertySetMask()
                 & AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0) {
@@ -595,6 +599,8 @@
         private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
         private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
         private int mMaxSharedAudioHistoryMs = 0;
+        private int mCallRedirectionMode = AudioManager.CALL_REDIRECT_NONE;
+
         private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
         private static final int PRIVACY_SENSITIVE_DISABLED = 0;
         private static final int PRIVACY_SENSITIVE_ENABLED = 1;
@@ -791,6 +797,65 @@
 
         /**
          * @hide
+         * Sets the {@link AudioRecord} call redirection mode.
+         * Used when creating an AudioRecord to extract audio from call downlink path. The mode
+         * indicates if the call is a PSTN call or a VoIP call in which case a dynamic audio
+         * policy is created to forward all playback with voice communication usage this record.
+         *
+         * @param callRedirectionMode one of
+         * {@link AudioManager#CALL_REDIRECT_NONE},
+         * {@link AudioManager#CALL_REDIRECT_PSTN},
+         * or {@link AAudioManager#CALL_REDIRECT_VOIP}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException if {@code callRedirectionMode} is not valid.
+         */
+        public @NonNull Builder setCallRedirectionMode(
+                @AudioManager.CallRedirectionMode int callRedirectionMode) {
+            switch (callRedirectionMode) {
+                case AudioManager.CALL_REDIRECT_NONE:
+                case AudioManager.CALL_REDIRECT_PSTN:
+                case AudioManager.CALL_REDIRECT_VOIP:
+                    mCallRedirectionMode = callRedirectionMode;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid call redirection mode " + callRedirectionMode);
+            }
+            return this;
+        }
+
+        private @NonNull AudioRecord buildCallExtractionRecord() {
+            AudioMixingRule audioMixingRule = new AudioMixingRule.Builder()
+                    .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE,
+                            new AudioAttributes.Builder()
+                                .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                                .setForCallRedirection()
+                                .build())
+                    .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE,
+                            new AudioAttributes.Builder()
+                                .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
+                                .setForCallRedirection()
+                                .build())
+                    .setTargetMixRole(AudioMixingRule.MIX_ROLE_PLAYERS)
+                    .build();
+            AudioMix audioMix = new AudioMix.Builder(audioMixingRule)
+                    .setFormat(mFormat)
+                    .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+                    .build();
+            AudioPolicy audioPolicy = new AudioPolicy.Builder(null).addMix(audioMix).build();
+            if (AudioManager.registerAudioPolicyStatic(audioPolicy) != 0) {
+                throw new UnsupportedOperationException("Error: could not register audio policy");
+            }
+            AudioRecord record = audioPolicy.createAudioRecordSink(audioMix);
+            if (record == null) {
+                throw new UnsupportedOperationException("Cannot create extraction AudioRecord");
+            }
+            record.unregisterAudioPolicyOnRelease(audioPolicy);
+            return record;
+        }
+
+        /**
+         * @hide
          * Specifies the maximum duration in the past of the this AudioRecord's capture buffer
          * that can be shared with another app by calling
          * {@link AudioRecord#shareAudioHistory(String, long)}.
@@ -897,6 +962,14 @@
                         .build();
             }
 
+            if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_VOIP) {
+                return buildCallExtractionRecord();
+            } else if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_PSTN) {
+                mAttributes = new AudioAttributes.Builder(mAttributes)
+                        .setForCallRedirection()
+                        .build();
+            }
+
             try {
                 // If the buffer size is not specified,
                 // use a single frame for the buffer size and let the
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index cc37c38..e8792b3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1915,7 +1915,7 @@
             types[i] = devices.get(i).getInternalType();
             if (types[i] == AudioSystem.DEVICE_NONE) {
                 types[i] = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(
-                        devices.get(i).getType());
+                        devices.get(i).getType(), devices.get(i).getAddress());
             }
             addresses[i] = devices.get(i).getAddress();
         }
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 0450a80..ea692b9 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -26,6 +26,9 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
 import android.media.metrics.LogSessionId;
 import android.os.Binder;
 import android.os.Build;
@@ -574,6 +577,8 @@
      */
     @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
 
+    private AudioPolicy mAudioPolicy;
+
     //--------------------------------
     // Used exclusively by native code
     //--------------------
@@ -1032,6 +1037,7 @@
         private int mPerformanceMode = PERFORMANCE_MODE_NONE;
         private boolean mOffload = false;
         private TunerConfiguration mTunerConfiguration;
+        private int mCallRedirectionMode = AudioManager.CALL_REDIRECT_NONE;
 
         /**
          * Constructs a new Builder with the default values as described above.
@@ -1227,6 +1233,74 @@
         }
 
         /**
+         * 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
+         * indicates if the call is a PSTN call or a VoIP call in which case a dynamic audio
+         * policy is created to use this track as the source for all capture with voice
+         * communication preset.
+         *
+         * @param callRedirectionMode one of
+         * {@link AudioManager#CALL_REDIRECT_NONE},
+         * {@link AudioManager#CALL_REDIRECT_PSTN},
+         * or {@link AAudioManager#CALL_REDIRECT_VOIP}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException if {@code callRedirectionMode} is not valid.
+         */
+        public @NonNull Builder setCallRedirectionMode(
+                @AudioManager.CallRedirectionMode int callRedirectionMode) {
+            switch (callRedirectionMode) {
+                case AudioManager.CALL_REDIRECT_NONE:
+                case AudioManager.CALL_REDIRECT_PSTN:
+                case AudioManager.CALL_REDIRECT_VOIP:
+                    mCallRedirectionMode = callRedirectionMode;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Invalid call redirection mode " + callRedirectionMode);
+            }
+            return this;
+        }
+
+        private @NonNull AudioTrack buildCallInjectionTrack() {
+            AudioMixingRule audioMixingRule = new AudioMixingRule.Builder()
+                    .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                            new AudioAttributes.Builder()
+                                   .setCapturePreset(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
+                                   .setForCallRedirection()
+                                   .build())
+                    .setTargetMixRole(AudioMixingRule.MIX_ROLE_INJECTOR)
+                    .build();
+            AudioMix audioMix = new AudioMix.Builder(audioMixingRule)
+                    .setFormat(mFormat)
+                    .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+                    .build();
+            AudioPolicy audioPolicy =
+                    new AudioPolicy.Builder(/*context=*/ null).addMix(audioMix).build();
+            if (AudioManager.registerAudioPolicyStatic(audioPolicy) != 0) {
+                throw new UnsupportedOperationException("Error: could not register audio policy");
+            }
+            AudioTrack track = audioPolicy.createAudioTrackSource(audioMix);
+            if (track == null) {
+                throw new UnsupportedOperationException("Cannot create injection AudioTrack");
+            }
+            track.unregisterAudioPolicyOnRelease(audioPolicy);
+            return track;
+        }
+
+        /**
          * Builds an {@link AudioTrack} instance initialized with all the parameters set
          * on this <code>Builder</code>.
          * @return a new successfully initialized {@link AudioTrack} instance.
@@ -1270,6 +1344,14 @@
                         .build();
             }
 
+            if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_VOIP) {
+                return buildCallInjectionTrack();
+            } else if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_PSTN) {
+                mAttributes = new AudioAttributes.Builder(mAttributes)
+                        .setForCallRedirection()
+                        .build();
+            }
+
             if (mOffload) {
                 if (mPerformanceMode == PERFORMANCE_MODE_LOW_LATENCY) {
                     throw new UnsupportedOperationException(
@@ -1315,6 +1397,16 @@
     }
 
     /**
+     * Sets an {@link AudioPolicy} to automatically unregister when the track is released.
+     *
+     * <p>This is to prevent users of the call audio injection API from having to manually
+     * unregister the policy that was used to create the track.
+     */
+    private void unregisterAudioPolicyOnRelease(AudioPolicy audioPolicy) {
+        mAudioPolicy = audioPolicy;
+    }
+
+    /**
      * Configures the delay and padding values for the current compressed stream playing
      * in offload mode.
      * This can only be used on a track successfully initialized with
@@ -1879,6 +1971,11 @@
         } catch(IllegalStateException ise) {
             // don't raise an exception, we're releasing the resources.
         }
+        if (mAudioPolicy != null) {
+            AudioManager.unregisterAudioPolicyAsyncStatic(mAudioPolicy);
+            mAudioPolicy = null;
+        }
+
         baseRelease();
         native_release();
         synchronized (mPlayStateLock) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 35191bd..7f6fb90 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -32,6 +32,7 @@
 import android.media.IAudioServerStateDispatcher;
 import android.media.ICapturePresetDevicesRoleDispatcher;
 import android.media.ICommunicationDeviceDispatcher;
+import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
@@ -383,7 +384,7 @@
 
     int requestAudioFocusForTest(in AudioAttributes aa, int durationHint, IBinder cb,
             in IAudioFocusDispatcher fd, in String clientId, in String callingPackageName,
-            int uid, int sdk);
+            int flags, int uid, int sdk);
 
     int abandonAudioFocusForTest(in IAudioFocusDispatcher fd, in String clientId,
             in AudioAttributes aa, in String callingPackageName);
@@ -445,4 +446,22 @@
     void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
 
     boolean isVolumeFixed();
+
+    boolean isPstnCallAudioInterceptable();
+
+    oneway void muteAwaitConnection(in int[] usagesToMute, in AudioDeviceAttributes dev,
+            long timeOutMs);
+
+    oneway void cancelMuteAwaitConnection(in AudioDeviceAttributes dev);
+
+    AudioDeviceAttributes getMutingExpectedDevice();
+
+    void registerMuteAwaitConnectionDispatcher(in IMuteAwaitConnectionCallback cb,
+            boolean register);
+
+    void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
+
+    List<AudioFocusInfo> getFocusStack();
+
+    boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java b/media/java/android/media/IMuteAwaitConnectionCallback.aidl
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
copy to media/java/android/media/IMuteAwaitConnectionCallback.aidl
index fbb78c8..77fc029 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.java
+++ b/media/java/android/media/IMuteAwaitConnectionCallback.aidl
@@ -14,12 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.flags;
+package android.media;
+
+import android.media.AudioDeviceAttributes;
 
 /**
- * Class to manage simple DeviceConfig-based feature flags.
+ * AIDL for the AudioService to signal mute events tied to audio device connections.
  *
- * See {@link Flags} for instructions on defining new flags.
+ * {@hide}
  */
-public interface FeatureFlags extends FlagReader {
+oneway interface IMuteAwaitConnectionCallback {
+
+    void dispatchOnMutedUntilConnection(in AudioDeviceAttributes device, in int[] mutedUsages);
+
+    void dispatchOnUnmutedEvent(int event, in AudioDeviceAttributes device, in int[] mutedUsages);
 }
diff --git a/media/java/android/media/MediaDescription.java b/media/java/android/media/MediaDescription.java
index 458562a..dece6bd 100644
--- a/media/java/android/media/MediaDescription.java
+++ b/media/java/android/media/MediaDescription.java
@@ -142,10 +142,10 @@
         mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mIcon = in.readParcelable(null);
-        mIconUri = in.readParcelable(null);
+        mIcon = in.readParcelable(null, android.graphics.Bitmap.class);
+        mIconUri = in.readParcelable(null, android.net.Uri.class);
         mExtras = in.readBundle();
-        mMediaUri = in.readParcelable(null);
+        mMediaUri = in.readParcelable(null, android.net.Uri.class);
     }
 
     /**
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 5f56a73..7459e63 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -250,15 +250,10 @@
             @NonNull FileDescriptor fd, long offset, long length) throws IOException;
 
     /**
-     * Sets the MediaCas instance to use. This should be called after a
-     * successful setDataSource() if at least one track reports mime type
-     * of {@link android.media.MediaFormat#MIMETYPE_AUDIO_SCRAMBLED}
-     * or {@link android.media.MediaFormat#MIMETYPE_VIDEO_SCRAMBLED}.
-     * Stream parsing will not proceed until a valid MediaCas object
-     * is provided.
-     *
-     * @param mediaCas the MediaCas object to use.
+     * @deprecated Use the {@code Descrambler} system API instead, or DRM public APIs like
+     *             {@link MediaDrm}.
      */
+    @Deprecated
     public final void setMediaCas(@NonNull MediaCas mediaCas) {
         mMediaCas = mediaCas;
         nativeSetMediaCas(mediaCas.getBinder());
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 9c9e83b..2427fa6 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -371,7 +371,7 @@
         mFeatures = in.createStringArrayList();
         mType = in.readInt();
         mIsSystem = in.readBoolean();
-        mIconUri = in.readParcelable(null);
+        mIconUri = in.readParcelable(null, android.net.Uri.class);
         mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mConnectionState = in.readInt();
         mClientPackageName = in.readString();
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 70bb960..28f238e 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -511,7 +511,12 @@
         int deviceType = AudioSystem.DEVICE_NONE;
         String deviceAddress = "";
         if (device != null) {
-            deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
+            if (device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT) {
+                deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
+            } else {
+                deviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(
+                        device.getType(), device.getAddress());
+            }
             deviceAddress = device.getAddress();
         }
 
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 8080aad..f85bdee 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -241,6 +241,11 @@
     }
 
     /** @hide */
+    public boolean isForCallRedirection() {
+        return mRule.isForCallRedirection();
+    }
+
+    /** @hide */
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 6112290..c912759 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.media.AudioAttributes;
+import android.media.MediaRecorder;
 import android.os.Build;
 import android.os.Parcel;
 import android.util.Log;
@@ -262,6 +263,22 @@
     }
 
     /** @hide */
+    public boolean isForCallRedirection() {
+        for (AudioMixMatchCriterion criterion : mCriteria) {
+            if (criterion.mAttr != null
+                    && (criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
+                        && criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                    || (criterion.mRule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET
+                        && criterion.mAttr.getCapturePreset()
+                            == MediaRecorder.AudioSource.VOICE_COMMUNICATION)
+                    && criterion.mAttr.isForCallRedirection()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** @hide */
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 3e8d76a..3ba1d1f 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
@@ -230,7 +231,7 @@
          * If set to {@code true}, it is mandatory to set an
          * {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build
          * an {@code AudioPolicy} instance.
-         * @param enforce true if the policy will govern audio focus decisions.
+         * @param isFocusPolicy true if the policy will govern audio focus decisions.
          * @return the same Builder instance.
          */
         @NonNull
@@ -588,6 +589,10 @@
         boolean canModifyAudioRouting = PackageManager.PERMISSION_GRANTED
                 == checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
 
+        boolean canInterceptCallAudio = PackageManager.PERMISSION_GRANTED
+                == checkCallingOrSelfPermission(
+                        android.Manifest.permission.CALL_AUDIO_INTERCEPTION);
+
         boolean canProjectAudio;
         try {
             canProjectAudio = mProjection != null && mProjection.getProjection().canProjectAudio();
@@ -596,7 +601,9 @@
             throw e.rethrowFromSystemServer();
         }
 
-        if (!((isLoopbackRenderPolicy() && canProjectAudio) || canModifyAudioRouting)) {
+        if (!((isLoopbackRenderPolicy() && canProjectAudio)
+                || (isCallRedirectionPolicy() && canInterceptCallAudio)
+                || canModifyAudioRouting)) {
             Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
                     + Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING or "
                     + "MediaProjection that can project audio.");
@@ -612,6 +619,17 @@
         }
     }
 
+    private boolean isCallRedirectionPolicy() {
+        synchronized (mLock) {
+            for (AudioMix mix : mConfig.mMixes) {
+                if (mix.isForCallRedirection()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
     /**
      * Returns {@link PackageManager#PERMISSION_GRANTED} if the caller has the given permission.
      */
@@ -706,6 +724,45 @@
     }
 
     /**
+     * Returns the list of entries in the focus stack.
+     * The list is ordered with increasing rank of focus ownership, where the last entry is at the
+     * top of the focus stack and is the current focus owner.
+     * @return the ordered list of focus owners
+     * @see AudioManager#registerAudioPolicy(AudioPolicy)
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public @NonNull List<AudioFocusInfo> getFocusStack() {
+        try {
+            return getService().getFocusStack();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus
+     * loss, and for it to exit the focus stack (its focus listener will not be invoked after that).
+     * This operation is only valid for a registered policy (with
+     * {@link AudioManager#registerAudioPolicy(AudioPolicy)}) that is also set as the policy focus
+     * listener (with {@link Builder#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
+     * @param focusLoser the stack entry that is exiting the stack through a focus loss
+     * @return false if the focusLoser wasn't found in the stack, true otherwise
+     * @throws IllegalStateException if used on an unregistered policy, or a registered policy
+     *     with no {@link AudioPolicyFocusListener} set
+     * @see AudioManager#registerAudioPolicy(AudioPolicy)
+     * @see Builder#setAudioPolicyStatusListener(AudioPolicyStatusListener)
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) throws IllegalStateException {
+        Objects.requireNonNull(focusLoser);
+        try {
+            return getService().sendFocusLoss(focusLoser, cb());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
      * Audio buffers recorded through the created instance will contain the mix of the audio
      * streams that fed the given mixer.
@@ -728,13 +785,16 @@
                 .setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
                         mix.getFormat().getChannelMask()))
                 .build();
+
+        AudioAttributes.Builder ab = new AudioAttributes.Builder()
+                .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
+                .addTag(addressForTag(mix))
+                .addTag(AudioRecord.SUBMIX_FIXED_VOLUME);
+        if (mix.isForCallRedirection()) {
+            ab.setForCallRedirection();
+        }
         // create the AudioRecord, configured for loop back, using the same format as the mix
-        AudioRecord ar = new AudioRecord(
-                new AudioAttributes.Builder()
-                        .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
-                        .addTag(addressForTag(mix))
-                        .addTag(AudioRecord.SUBMIX_FIXED_VOLUME)
-                        .build(),
+        AudioRecord ar = new AudioRecord(ab.build(),
                 mixFormat,
                 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
                         // using stereo for buffer size to avoid the current poor support for masks
@@ -768,11 +828,13 @@
         }
         checkMixReadyToUse(mix, true/*for an AudioTrack*/);
         // create the AudioTrack, configured for loop back, using the same format as the mix
-        AudioTrack at = new AudioTrack(
-                new AudioAttributes.Builder()
-                        .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
-                        .addTag(addressForTag(mix))
-                        .build(),
+        AudioAttributes.Builder ab = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
+                .addTag(addressForTag(mix));
+        if (mix.isForCallRedirection()) {
+            ab.setForCallRedirection();
+        }
+        AudioTrack at = new AudioTrack(ab.build(),
                 mix.getFormat(),
                 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
                         mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
diff --git a/media/java/android/media/midi/MidiDeviceStatus.java b/media/java/android/media/midi/MidiDeviceStatus.java
index b118279..aa06267 100644
--- a/media/java/android/media/midi/MidiDeviceStatus.java
+++ b/media/java/android/media/midi/MidiDeviceStatus.java
@@ -115,7 +115,7 @@
         new Parcelable.Creator<MidiDeviceStatus>() {
         public MidiDeviceStatus createFromParcel(Parcel in) {
             ClassLoader classLoader = MidiDeviceInfo.class.getClassLoader();
-            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader);
+            MidiDeviceInfo deviceInfo = in.readParcelable(classLoader, android.media.midi.MidiDeviceInfo.class);
             boolean[] inputPortOpen = in.createBooleanArray();
             int[] outputPortOpenCount = in.createIntArray();
             return new MidiDeviceStatus(deviceInfo, inputPortOpen, outputPortOpenCount);
diff --git a/media/java/android/media/musicrecognition/RecognitionRequest.java b/media/java/android/media/musicrecognition/RecognitionRequest.java
index 3298d63..b8757a3 100644
--- a/media/java/android/media/musicrecognition/RecognitionRequest.java
+++ b/media/java/android/media/musicrecognition/RecognitionRequest.java
@@ -152,8 +152,8 @@
     }
 
     private RecognitionRequest(Parcel in) {
-        mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader());
-        mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader());
+        mAudioFormat = in.readParcelable(AudioFormat.class.getClassLoader(), android.media.AudioFormat.class);
+        mAudioAttributes = in.readParcelable(AudioAttributes.class.getClassLoader(), android.media.AudioAttributes.class);
         mCaptureSession = in.readInt();
         mMaxAudioLengthSeconds = in.readInt();
         mIgnoreBeginningFrames = in.readInt();
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1da41fb..955ae3c 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -1022,7 +1022,7 @@
             mVolumeControl = in.readInt();
             mMaxVolume = in.readInt();
             mCurrentVolume = in.readInt();
-            mAudioAttrs = in.readParcelable(null);
+            mAudioAttrs = in.readParcelable(null, android.media.AudioAttributes.class);
             mVolumeControlId = in.readString();
         }
 
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 4d8eda1..c439356 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -23,19 +23,35 @@
 
 /** @hide */
 public abstract class BroadcastInfoRequest implements Parcelable {
-    protected static final int PARCEL_TOKEN_TS_REQUEST = 1;
+
+    // todo: change const declaration to intdef
+    public static final int REQUEST_OPTION_REPEAT = 11;
+    public static final int REQUEST_OPTION_AUTO_UPDATE = 12;
 
     public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
             new Parcelable.Creator<BroadcastInfoRequest>() {
                 @Override
                 public BroadcastInfoRequest createFromParcel(Parcel source) {
-                    int token = source.readInt();
-                    switch (token) {
-                        case PARCEL_TOKEN_TS_REQUEST:
+                    int type = source.readInt();
+                    switch (type) {
+                        case BroadcastInfoType.TS:
                             return TsRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.TABLE:
+                            return TableRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.SECTION:
+                            return SectionRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.PES:
+                            return PesRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.STREAM_EVENT:
+                            return StreamEventRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.DSMCC:
+                            return DsmccRequest.createFromParcelBody(source);
+                        case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+                            return TvProprietaryFunctionRequest.createFromParcelBody(source);
                         default:
                             throw new IllegalStateException(
-                                    "Unexpected broadcast info request type token in parcel.");
+                                    "Unexpected broadcast info request type (value "
+                                            + type + ") in parcel.");
                     }
                 }
 
@@ -45,18 +61,32 @@
                 }
             };
 
-    int requestId;
+    protected final int mType;
+    protected final int mRequestId;
+    protected final int mOption;
 
-    public BroadcastInfoRequest(int requestId) {
-        this.requestId = requestId;
+    protected BroadcastInfoRequest(int type, int requestId, int option) {
+        mType = type;
+        mRequestId = requestId;
+        mOption = option;
     }
 
-    protected BroadcastInfoRequest(Parcel source) {
-        requestId = source.readInt();
+    protected BroadcastInfoRequest(int type, Parcel source) {
+        mType = type;
+        mRequestId = source.readInt();
+        mOption = source.readInt();
+    }
+
+    public int getType() {
+        return mType;
     }
 
     public int getRequestId() {
-        return requestId;
+        return mRequestId;
+    }
+
+    public int getOption() {
+        return mOption;
     }
 
     @Override
@@ -66,6 +96,8 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(requestId);
+        dest.writeInt(mType);
+        dest.writeInt(mRequestId);
+        dest.writeInt(mOption);
     }
 }
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index fe4e8b7..288f2f9 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -22,12 +22,37 @@
 import android.annotation.NonNull;
 
 /** @hide */
-public final class BroadcastInfoResponse implements Parcelable {
+public abstract class BroadcastInfoResponse implements Parcelable {
+    // todo: change const declaration to intdef
+    public static final int ERROR = 1;
+    public static final int OK = 2;
+    public static final int CANCEL = 3;
+
     public static final @NonNull Parcelable.Creator<BroadcastInfoResponse> CREATOR =
             new Parcelable.Creator<BroadcastInfoResponse>() {
                 @Override
                 public BroadcastInfoResponse createFromParcel(Parcel source) {
-                    return new BroadcastInfoResponse(source);
+                    int type = source.readInt();
+                    switch (type) {
+                        case BroadcastInfoType.TS:
+                            return TsResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.TABLE:
+                            return TableResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.SECTION:
+                            return SectionResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.PES:
+                            return PesResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.STREAM_EVENT:
+                            return StreamEventResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.DSMCC:
+                            return DsmccResponse.createFromParcelBody(source);
+                        case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+                            return TvProprietaryFunctionResponse.createFromParcelBody(source);
+                        default:
+                            throw new IllegalStateException(
+                                    "Unexpected broadcast info response type (value "
+                                            + type + ") in parcel.");
+                    }
                 }
 
                 @Override
@@ -36,18 +61,39 @@
                 }
             };
 
-    int requestId;
+    protected final int mType;
+    protected final int mRequestId;
+    protected final int mSequence;
+    protected final int mResponseResult;
 
-    public BroadcastInfoResponse(int requestId) {
-        this.requestId = requestId;
+    protected BroadcastInfoResponse(int type, int requestId, int sequence, int responseResult) {
+        mType = type;
+        mRequestId = requestId;
+        mSequence = sequence;
+        mResponseResult = responseResult;
     }
 
-    private BroadcastInfoResponse(Parcel source) {
-        requestId = source.readInt();
+    protected BroadcastInfoResponse(int type, Parcel source) {
+        mType = type;
+        mRequestId = source.readInt();
+        mSequence = source.readInt();
+        mResponseResult = source.readInt();
+    }
+
+    public int getType() {
+        return mType;
     }
 
     public int getRequestId() {
-        return requestId;
+        return mRequestId;
+    }
+
+    public int getSequence() {
+        return mSequence;
+    }
+
+    public int getResponseResult() {
+        return mResponseResult;
     }
 
     @Override
@@ -57,6 +103,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(requestId);
+        dest.writeInt(mType);
+        dest.writeInt(mRequestId);
+        dest.writeInt(mSequence);
+        dest.writeInt(mResponseResult);
     }
 }
diff --git a/media/java/android/media/tv/BroadcastInfoType.java b/media/java/android/media/tv/BroadcastInfoType.java
new file mode 100644
index 0000000..e7a0595
--- /dev/null
+++ b/media/java/android/media/tv/BroadcastInfoType.java
@@ -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 android.media.tv;
+
+/** @hide */
+public final class BroadcastInfoType {
+    // todo: change const declaration to intdef in TvInputManager
+    public static final int TS = 1;
+    public static final int TABLE = 2;
+    public static final int SECTION = 3;
+    public static final int PES = 4;
+    public static final int STREAM_EVENT = 5;
+    public static final int DSMCC = 6;
+    public static final int TV_PROPRIETARY_FUNCTION = 7;
+
+    private BroadcastInfoType() {
+    }
+}
diff --git a/media/java/android/media/tv/DsmccRequest.java b/media/java/android/media/tv/DsmccRequest.java
new file mode 100644
index 0000000..f2e4750
--- /dev/null
+++ b/media/java/android/media/tv/DsmccRequest.java
@@ -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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.DSMCC;
+
+    public static final @NonNull Parcelable.Creator<DsmccRequest> CREATOR =
+            new Parcelable.Creator<DsmccRequest>() {
+                @Override
+                public DsmccRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public DsmccRequest[] newArray(int size) {
+                    return new DsmccRequest[size];
+                }
+            };
+
+    private final Uri mUri;
+
+    public static DsmccRequest createFromParcelBody(Parcel in) {
+        return new DsmccRequest(in);
+    }
+
+    public DsmccRequest(int requestId, int option, Uri uri) {
+        super(requestType, requestId, option);
+        mUri = uri;
+    }
+
+    protected DsmccRequest(Parcel source) {
+        super(requestType, source);
+        String uriString = source.readString();
+        mUri = uriString == null ? null : Uri.parse(uriString);
+    }
+
+    public Uri getUri() {
+        return mUri;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        String uriString = mUri == null ? null : mUri.toString();
+        dest.writeString(uriString);
+    }
+}
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
new file mode 100644
index 0000000..3bdfb95
--- /dev/null
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -0,0 +1,68 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.DSMCC;
+
+    public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR =
+            new Parcelable.Creator<DsmccResponse>() {
+                @Override
+                public DsmccResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public DsmccResponse[] newArray(int size) {
+                    return new DsmccResponse[size];
+                }
+            };
+
+    private final ParcelFileDescriptor mFile;
+
+    public static DsmccResponse createFromParcelBody(Parcel in) {
+        return new DsmccResponse(in);
+    }
+
+    public DsmccResponse(int requestId, int sequence, int responseResult,
+            ParcelFileDescriptor file) {
+        super(responseType, requestId, sequence, responseResult);
+        mFile = file;
+    }
+
+    protected DsmccResponse(Parcel source) {
+        super(responseType, source);
+        mFile = source.readFileDescriptor();
+    }
+
+    public ParcelFileDescriptor getFile() {
+        return mFile;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        mFile.writeToParcel(dest, flags);
+    }
+}
diff --git a/media/java/android/media/tv/PesRequest.java b/media/java/android/media/tv/PesRequest.java
new file mode 100644
index 0000000..0e444b8
--- /dev/null
+++ b/media/java/android/media/tv/PesRequest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.PES;
+
+    public static final @NonNull Parcelable.Creator<PesRequest> CREATOR =
+            new Parcelable.Creator<PesRequest>() {
+                @Override
+                public PesRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public PesRequest[] newArray(int size) {
+                    return new PesRequest[size];
+                }
+            };
+
+    private final int mTsPid;
+    private final int mStreamId;
+
+    public static PesRequest createFromParcelBody(Parcel in) {
+        return new PesRequest(in);
+    }
+
+    public PesRequest(int requestId, int option, int tsPid, int streamId) {
+        super(requestType, requestId, option);
+        mTsPid = tsPid;
+        mStreamId = streamId;
+    }
+
+    protected PesRequest(Parcel source) {
+        super(requestType, source);
+        mTsPid = source.readInt();
+        mStreamId = source.readInt();
+    }
+
+    public int getTsPid() {
+        return mTsPid;
+    }
+
+    public int getStreamId() {
+        return mStreamId;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mTsPid);
+        dest.writeInt(mStreamId);
+    }
+}
diff --git a/media/java/android/media/tv/PesResponse.java b/media/java/android/media/tv/PesResponse.java
new file mode 100644
index 0000000..d46e6fc
--- /dev/null
+++ b/media/java/android/media/tv/PesResponse.java
@@ -0,0 +1,66 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.PES;
+
+    public static final @NonNull Parcelable.Creator<PesResponse> CREATOR =
+            new Parcelable.Creator<PesResponse>() {
+                @Override
+                public PesResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public PesResponse[] newArray(int size) {
+                    return new PesResponse[size];
+                }
+            };
+
+    private final String mSharedFilterToken;
+
+    public static PesResponse createFromParcelBody(Parcel in) {
+        return new PesResponse(in);
+    }
+
+    public PesResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+        super(responseType, requestId, sequence, responseResult);
+        mSharedFilterToken = sharedFilterToken;
+    }
+
+    protected PesResponse(Parcel source) {
+        super(responseType, source);
+        mSharedFilterToken = source.readString();
+    }
+
+    public String getSharedFilterToken() {
+        return mSharedFilterToken;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mSharedFilterToken);
+    }
+}
diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
new file mode 100644
index 0000000..3e8e909
--- /dev/null
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.SECTION;
+
+    public static final @NonNull Parcelable.Creator<SectionRequest> CREATOR =
+            new Parcelable.Creator<SectionRequest>() {
+                @Override
+                public SectionRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public SectionRequest[] newArray(int size) {
+                    return new SectionRequest[size];
+                }
+            };
+
+    private final int mTsPid;
+    private final int mTableId;
+    private final int mVersion;
+
+    public static SectionRequest createFromParcelBody(Parcel in) {
+        return new SectionRequest(in);
+    }
+
+    public SectionRequest(int requestId, int option, int tsPid, int tableId, int version) {
+        super(requestType, requestId, option);
+        mTsPid = tsPid;
+        mTableId = tableId;
+        mVersion = version;
+    }
+
+    protected SectionRequest(Parcel source) {
+        super(requestType, source);
+        mTsPid = source.readInt();
+        mTableId = source.readInt();
+        mVersion = source.readInt();
+    }
+
+    public int getTsPid() {
+        return mTsPid;
+    }
+
+    public int getTableId() {
+        return mTableId;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mTsPid);
+        dest.writeInt(mTableId);
+        dest.writeInt(mVersion);
+    }
+}
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
new file mode 100644
index 0000000..1c8f965
--- /dev/null
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -0,0 +1,84 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.SECTION;
+
+    public static final @NonNull Parcelable.Creator<SectionResponse> CREATOR =
+            new Parcelable.Creator<SectionResponse>() {
+                @Override
+                public SectionResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public SectionResponse[] newArray(int size) {
+                    return new SectionResponse[size];
+                }
+            };
+
+    private final int mSessionId;
+    private final int mVersion;
+    private final Bundle mSessionData;
+
+    public static SectionResponse createFromParcelBody(Parcel in) {
+        return new SectionResponse(in);
+    }
+
+    public SectionResponse(int requestId, int sequence, int responseResult, int sessionId,
+            int version, Bundle sessionData) {
+        super(responseType, requestId, sequence, responseResult);
+        mSessionId = sessionId;
+        mVersion = version;
+        mSessionData = sessionData;
+    }
+
+    protected SectionResponse(Parcel source) {
+        super(responseType, source);
+        mSessionId = source.readInt();
+        mVersion = source.readInt();
+        mSessionData = source.readBundle();
+    }
+
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    public Bundle getSessionData() {
+        return mSessionData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mSessionId);
+        dest.writeInt(mVersion);
+        dest.writeBundle(mSessionData);
+    }
+}
diff --git a/media/java/android/media/tv/StreamEventRequest.java b/media/java/android/media/tv/StreamEventRequest.java
new file mode 100644
index 0000000..09399c2
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventRequest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.STREAM_EVENT;
+
+    public static final @NonNull Parcelable.Creator<StreamEventRequest> CREATOR =
+            new Parcelable.Creator<StreamEventRequest>() {
+                @Override
+                public StreamEventRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public StreamEventRequest[] newArray(int size) {
+                    return new StreamEventRequest[size];
+                }
+            };
+
+    private final Uri mTargetUri;
+    private final String mEventName;
+
+    public static StreamEventRequest createFromParcelBody(Parcel in) {
+        return new StreamEventRequest(in);
+    }
+
+    public StreamEventRequest(int requestId, int option, Uri targetUri, String eventName) {
+        super(requestType, requestId, option);
+        this.mTargetUri = targetUri;
+        this.mEventName = eventName;
+    }
+
+    protected StreamEventRequest(Parcel source) {
+        super(requestType, source);
+        String uriString = source.readString();
+        mTargetUri = uriString == null ? null : Uri.parse(uriString);
+        mEventName = source.readString();
+    }
+
+    public Uri getTargetUri() {
+        return mTargetUri;
+    }
+
+    public String getEventName() {
+        return mEventName;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        String uriString = mTargetUri == null ? null : mTargetUri.toString();
+        dest.writeString(uriString);
+        dest.writeString(mEventName);
+    }
+}
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
new file mode 100644
index 0000000..027b735
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -0,0 +1,91 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.STREAM_EVENT;
+
+    public static final @NonNull Parcelable.Creator<StreamEventResponse> CREATOR =
+            new Parcelable.Creator<StreamEventResponse>() {
+                @Override
+                public StreamEventResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public StreamEventResponse[] newArray(int size) {
+                    return new StreamEventResponse[size];
+                }
+            };
+
+    private final String mName;
+    private final String mText;
+    private final String mData;
+    private final String mStatus;
+
+    public static StreamEventResponse createFromParcelBody(Parcel in) {
+        return new StreamEventResponse(in);
+    }
+
+    public StreamEventResponse(int requestId, int sequence, int responseResult, String name,
+            String text, String data, String status) {
+        super(responseType, requestId, sequence, responseResult);
+        mName = name;
+        mText = text;
+        mData = data;
+        mStatus = status;
+    }
+
+    protected StreamEventResponse(Parcel source) {
+        super(responseType, source);
+        mName = source.readString();
+        mText = source.readString();
+        mData = source.readString();
+        mStatus = source.readString();
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public String getData() {
+        return mData;
+    }
+
+    public String getStatus() {
+        return mStatus;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mName);
+        dest.writeString(mText);
+        dest.writeString(mData);
+        dest.writeString(mStatus);
+    }
+}
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
new file mode 100644
index 0000000..5432215
--- /dev/null
+++ b/media/java/android/media/tv/TableRequest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TableRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.TABLE;
+
+    // todo: change const declaration to intdef
+    public static final int PAT = 1;
+    public static final int PMT = 2;
+
+    public static final @NonNull Parcelable.Creator<TableRequest> CREATOR =
+            new Parcelable.Creator<TableRequest>() {
+                @Override
+                public TableRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TableRequest[] newArray(int size) {
+                    return new TableRequest[size];
+                }
+            };
+
+    private final int mTableId;
+    private final int mTableName;
+    private final int mVersion;
+
+    public static TableRequest createFromParcelBody(Parcel in) {
+        return new TableRequest(in);
+    }
+
+    public TableRequest(int requestId, int option, int tableId, int tableName, int version) {
+        super(requestType, requestId, option);
+        mTableId = tableId;
+        mTableName = tableName;
+        mVersion = version;
+    }
+
+    protected TableRequest(Parcel source) {
+        super(requestType, source);
+        mTableId = source.readInt();
+        mTableName = source.readInt();
+        mVersion = source.readInt();
+    }
+
+    public int getTableId() {
+        return mTableId;
+    }
+
+    public int getTableName() {
+        return mTableName;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeInt(mTableId);
+        dest.writeInt(mTableName);
+        dest.writeInt(mVersion);
+    }
+}
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
new file mode 100644
index 0000000..a6d3e39
--- /dev/null
+++ b/media/java/android/media/tv/TableResponse.java
@@ -0,0 +1,86 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.net.Uri;
+
+/** @hide */
+public class TableResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.TABLE;
+
+    public static final @NonNull Parcelable.Creator<TableResponse> CREATOR =
+            new Parcelable.Creator<TableResponse>() {
+                @Override
+                public TableResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TableResponse[] newArray(int size) {
+                    return new TableResponse[size];
+                }
+            };
+
+    private final Uri mTableUri;
+    private final int mVersion;
+    private final int mSize;
+
+    public static TableResponse createFromParcelBody(Parcel in) {
+        return new TableResponse(in);
+    }
+
+    public TableResponse(int requestId, int sequence, int responseResult, Uri tableUri,
+            int version, int size) {
+        super(responseType, requestId, sequence, responseResult);
+        mTableUri = tableUri;
+        mVersion = version;
+        mSize = size;
+    }
+
+    protected TableResponse(Parcel source) {
+        super(responseType, source);
+        String uriString = source.readString();
+        mTableUri = uriString == null ? null : Uri.parse(uriString);
+        mVersion = source.readInt();
+        mSize = source.readInt();
+    }
+
+    public Uri getTableUri() {
+        return mTableUri;
+    }
+
+    public int getVersion() {
+        return mVersion;
+    }
+
+    public int getSize() {
+        return mSize;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        String uriString = mTableUri == null ? null : mTableUri.toString();
+        dest.writeString(uriString);
+        dest.writeInt(mVersion);
+        dest.writeInt(mSize);
+    }
+}
diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java
index 3690d05..141f3ac 100644
--- a/media/java/android/media/tv/TsRequest.java
+++ b/media/java/android/media/tv/TsRequest.java
@@ -22,6 +22,8 @@
 
 /** @hide */
 public class TsRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.TS;
+
     public static final @NonNull Parcelable.Creator<TsRequest> CREATOR =
             new Parcelable.Creator<TsRequest>() {
                 @Override
@@ -36,30 +38,29 @@
                 }
             };
 
-    int tsPid;
+    private final int mTsPid;
 
     public static TsRequest createFromParcelBody(Parcel in) {
         return new TsRequest(in);
     }
 
-    public TsRequest(int requestId, int tsPid) {
-        super(requestId);
-        this.tsPid = tsPid;
+    public TsRequest(int requestId, int option, int tsPid) {
+        super(requestType, requestId, option);
+        mTsPid = tsPid;
     }
 
     protected TsRequest(Parcel source) {
-        super(source);
-        tsPid = source.readInt();
+        super(requestType, source);
+        mTsPid = source.readInt();
     }
 
     public int getTsPid() {
-        return tsPid;
+        return mTsPid;
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeInt(PARCEL_TOKEN_TS_REQUEST);
         super.writeToParcel(dest, flags);
-        dest.writeInt(tsPid);
+        dest.writeInt(mTsPid);
     }
 }
diff --git a/media/java/android/media/tv/TsResponse.java b/media/java/android/media/tv/TsResponse.java
new file mode 100644
index 0000000..e30ff54
--- /dev/null
+++ b/media/java/android/media/tv/TsResponse.java
@@ -0,0 +1,66 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TsResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.TS;
+
+    public static final @NonNull Parcelable.Creator<TsResponse> CREATOR =
+            new Parcelable.Creator<TsResponse>() {
+                @Override
+                public TsResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TsResponse[] newArray(int size) {
+                    return new TsResponse[size];
+                }
+            };
+
+    private final String mSharedFilterToken;
+
+    public static TsResponse createFromParcelBody(Parcel in) {
+        return new TsResponse(in);
+    }
+
+    public TsResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+        super(responseType, requestId, sequence, responseResult);
+        this.mSharedFilterToken = sharedFilterToken;
+    }
+
+    protected TsResponse(Parcel source) {
+        super(responseType, source);
+        mSharedFilterToken = source.readString();
+    }
+
+    public String getSharedFilterToken() {
+        return mSharedFilterToken;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mSharedFilterToken);
+    }
+}
diff --git a/media/java/android/media/tv/TvContentRatingSystemInfo.java b/media/java/android/media/tv/TvContentRatingSystemInfo.java
index f44ded3..947b2d6 100644
--- a/media/java/android/media/tv/TvContentRatingSystemInfo.java
+++ b/media/java/android/media/tv/TvContentRatingSystemInfo.java
@@ -94,8 +94,8 @@
     };
 
     private TvContentRatingSystemInfo(Parcel in) {
-        mXmlUri = in.readParcelable(null);
-        mApplicationInfo = in.readParcelable(null);
+        mXmlUri = in.readParcelable(null, android.net.Uri.class);
+        mApplicationInfo = in.readParcelable(null, android.content.pm.ApplicationInfo.class);
     }
 
     @Override
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index a0f6fb9..9147c12 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -2720,6 +2720,42 @@
          */
         public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
 
+        /**
+         * The flag indicating whether this TV program is scrambled or not.
+         *
+         * <p>Use the same coding for scrambled in the underlying broadcast standard
+         * if {@code free_ca_mode} in EIT is defined there (e.g. ETSI EN 300 468).
+         *
+         * <p>Type: INTEGER (boolean)
+         */
+        public static final String COLUMN_SCRAMBLED = "scrambled";
+
+        /**
+         * The comma-separated series IDs of this TV program for episodic TV shows.
+         *
+         * <p>This is used to indicate the series IDs.
+         * Programs in the same series share a series ID.
+         * Use this instead of {@link #COLUMN_SERIES_ID} if more than one series IDs
+         * are assigned to the TV program.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
+
+        /**
+         * The internal ID used by individual TV input services.
+         *
+         * <p>This is internal to the provider that inserted it, and should not be decoded by other
+         * apps.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
         private Programs() {}
 
         /** Canonical genres for TV programs. */
@@ -3052,6 +3088,32 @@
         public static final String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS =
                 "recording_expire_time_utc_millis";
 
+        /**
+         * The comma-separated series IDs of this TV program for episodic TV shows.
+         *
+         * <p>This is used to indicate the series IDs.
+         * Programs in the same series share a series ID.
+         * Use this instead of {@link #COLUMN_SERIES_ID} if more than one series IDs
+         * are assigned to the TV program.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
+
+        /**
+         * The internal ID used by individual TV input services.
+         *
+         * <p>This is internal to the provider that inserted it, and should not be decoded by other
+         * apps.
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
+
         private RecordedPrograms() {}
     }
 
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 54cb2bf..e60d537 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -653,16 +653,16 @@
         mType = in.readInt();
         mIsHardwareInput = in.readByte() == 1;
         mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-        mIconUri = in.readParcelable(null);
+        mIconUri = in.readParcelable(null, android.net.Uri.class);
         mLabelResId = in.readInt();
-        mIcon = in.readParcelable(null);
-        mIconStandby = in.readParcelable(null);
-        mIconDisconnected = in.readParcelable(null);
+        mIcon = in.readParcelable(null, android.graphics.drawable.Icon.class);
+        mIconStandby = in.readParcelable(null, android.graphics.drawable.Icon.class);
+        mIconDisconnected = in.readParcelable(null, android.graphics.drawable.Icon.class);
         mSetupActivity = in.readString();
         mCanRecord = in.readByte() == 1;
         mCanPauseRecording = in.readByte() == 1;
         mTunerCount = in.readInt();
-        mHdmiDeviceInfo = in.readParcelable(null);
+        mHdmiDeviceInfo = in.readParcelable(null, android.hardware.hdmi.HdmiDeviceInfo.class);
         mIsConnectedToHdmiSwitch = in.readByte() == 1;
         mHdmiConnectionRelativePosition = in.readInt();
         mParentId = in.readString();
diff --git a/media/java/android/media/tv/TvProprietaryFunctionRequest.java b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
new file mode 100644
index 0000000..845641d
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
@@ -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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionRequest extends BroadcastInfoRequest implements Parcelable {
+    public static final int requestType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+    public static final @NonNull Parcelable.Creator<TvProprietaryFunctionRequest> CREATOR =
+            new Parcelable.Creator<TvProprietaryFunctionRequest>() {
+                @Override
+                public TvProprietaryFunctionRequest createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TvProprietaryFunctionRequest[] newArray(int size) {
+                    return new TvProprietaryFunctionRequest[size];
+                }
+            };
+
+    private final String mNameSpace;
+    private final String mName;
+    private final String mArguments;
+
+    public static TvProprietaryFunctionRequest createFromParcelBody(Parcel in) {
+        return new TvProprietaryFunctionRequest(in);
+    }
+
+    public TvProprietaryFunctionRequest(int requestId, int option, String nameSpace,
+            String name, String arguments) {
+        super(requestType, requestId, option);
+        mNameSpace = nameSpace;
+        mName = name;
+        mArguments = arguments;
+    }
+
+    protected TvProprietaryFunctionRequest(Parcel source) {
+        super(requestType, source);
+        mNameSpace = source.readString();
+        mName = source.readString();
+        mArguments = source.readString();
+    }
+
+    public String getNameSpace() {
+        return mNameSpace;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getArguments() {
+        return mArguments;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mNameSpace);
+        dest.writeString(mName);
+        dest.writeString(mArguments);
+    }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionResponse.java b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
new file mode 100644
index 0000000..3181b08
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
@@ -0,0 +1,67 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionResponse extends BroadcastInfoResponse implements Parcelable {
+    public static final int responseType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+    public static final @NonNull Parcelable.Creator<TvProprietaryFunctionResponse> CREATOR =
+            new Parcelable.Creator<TvProprietaryFunctionResponse>() {
+                @Override
+                public TvProprietaryFunctionResponse createFromParcel(Parcel source) {
+                    source.readInt();
+                    return createFromParcelBody(source);
+                }
+
+                @Override
+                public TvProprietaryFunctionResponse[] newArray(int size) {
+                    return new TvProprietaryFunctionResponse[size];
+                }
+            };
+
+    private final String mResponse;
+
+    public static TvProprietaryFunctionResponse createFromParcelBody(Parcel in) {
+        return new TvProprietaryFunctionResponse(in);
+    }
+
+    public TvProprietaryFunctionResponse(int requestId, int sequence, int responseResult,
+            String response) {
+        super(responseType, requestId, sequence, responseResult);
+        mResponse = response;
+    }
+
+    protected TvProprietaryFunctionResponse(Parcel source) {
+        super(responseType, source);
+        mResponse = source.readString();
+    }
+
+    public String getResponse() {
+        return mResponse;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mResponse);
+    }
+}
diff --git a/media/java/android/media/tv/interactive/ITvIAppClient.aidl b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
index 9fc1fe7..a5f2331 100644
--- a/media/java/android/media/tv/interactive/ITvIAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppClient.aidl
@@ -15,9 +15,9 @@
  */
 
 package android.media.tv.interactive;
-import android.media.tv.BroadcastInfoRequest;
 
 import android.media.tv.BroadcastInfoRequest;
+import android.os.Bundle;
 import android.view.InputChannel;
 
 /**
@@ -30,4 +30,6 @@
     void onSessionReleased(int seq);
     void onLayoutSurface(int left, int top, int right, int bottom, int seq);
     void onBroadcastInfoRequest(in BroadcastInfoRequest request, int seq);
-}
\ No newline at end of file
+    void onSessionStateChanged(int state, int seq);
+    void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
+}
diff --git a/media/java/android/media/tv/interactive/ITvIAppManager.aidl b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
index cd87a09..40d8034 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManager.aidl
@@ -22,6 +22,7 @@
 import android.media.tv.interactive.ITvIAppManagerCallback;
 import android.media.tv.interactive.TvIAppInfo;
 import android.net.Uri;
+import android.os.Bundle;
 import android.view.Surface;
 
 /**
@@ -31,6 +32,8 @@
 interface ITvIAppManager {
     List<TvIAppInfo> getTvIAppServiceList(int userId);
     void prepare(String tiasId, int type, int userId);
+    void notifyAppLinkInfo(String tiasId, in Bundle info, int userId);
+    void sendAppLinkCommand(String tiasId, in Bundle command, int userId);
     void startIApp(in IBinder sessionToken, int userId);
     void createSession(
             in ITvIAppClient client, in String iAppServiceId, int type, int seq, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
index 77a09b7..d5e0c63 100644
--- a/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppManagerCallback.aidl
@@ -27,4 +27,5 @@
     void onIAppServiceRemoved(in String iAppServiceId);
     void onIAppServiceUpdated(in String iAppServiceId);
     void onTvIAppInfoUpdated(in TvIAppInfo tvIAppInfo);
+    void onStateChanged(in String iAppServiceId, int type, int state);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppService.aidl b/media/java/android/media/tv/interactive/ITvIAppService.aidl
index af15dd8..8acb75f 100644
--- a/media/java/android/media/tv/interactive/ITvIAppService.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppService.aidl
@@ -18,6 +18,7 @@
 
 import android.media.tv.interactive.ITvIAppServiceCallback;
 import android.media.tv.interactive.ITvIAppSessionCallback;
+import android.os.Bundle;
 import android.view.InputChannel;
 
 /**
@@ -31,4 +32,6 @@
     void createSession(in InputChannel channel, in ITvIAppSessionCallback callback,
             in String iAppServiceId, int type);
     void prepare(int type);
+    void notifyAppLinkInfo(in Bundle info);
+    void sendAppLinkCommand(in Bundle command);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
index 8d49bc2..fec7d78 100644
--- a/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppServiceCallback.aidl
@@ -22,4 +22,5 @@
  * @hide
  */
 oneway interface ITvIAppServiceCallback {
+    void onStateChanged(int type, int state);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
index d2b966e..66f5fc1 100644
--- a/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvIAppSessionCallback.aidl
@@ -19,6 +19,7 @@
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.interactive.ITvIAppSession;
 import android.media.tv.BroadcastInfoRequest;
+import android.os.Bundle;
 
 /**
  * Helper interface for ITvIAppSession to allow TvIAppService to notify the system service when
@@ -29,4 +30,6 @@
     void onSessionCreated(in ITvIAppSession session);
     void onLayoutSurface(int left, int top, int right, int bottom);
     void onBroadcastInfoRequest(in BroadcastInfoRequest request);
-}
\ No newline at end of file
+    void onSessionStateChanged(int state);
+    void onCommandRequest(in String cmdType, in Bundle parameters);
+}
diff --git a/media/java/android/media/tv/interactive/TvIAppManager.java b/media/java/android/media/tv/interactive/TvIAppManager.java
index fed9769..9390d8d 100644
--- a/media/java/android/media/tv/interactive/TvIAppManager.java
+++ b/media/java/android/media/tv/interactive/TvIAppManager.java
@@ -16,6 +16,7 @@
 
 package android.media.tv.interactive;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemService;
@@ -25,6 +26,7 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvInputManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -41,6 +43,8 @@
 
 import com.android.internal.util.Preconditions;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -54,6 +58,110 @@
     // TODO: cleanup and unhide public APIs
     private static final String TAG = "TvIAppManager";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "TV_IAPP_RTE_STATE_", value = {
+            TV_IAPP_RTE_STATE_UNREALIZED,
+            TV_IAPP_RTE_STATE_PREPARING,
+            TV_IAPP_RTE_STATE_READY,
+            TV_IAPP_RTE_STATE_ERROR})
+    public @interface TvIAppRteState {}
+
+    /**
+     * Unrealized state of interactive app RTE.
+     * @hide
+     */
+    public static final int TV_IAPP_RTE_STATE_UNREALIZED = 1;
+    /**
+     * Preparing state of interactive app RTE.
+     * @hide
+     */
+    public static final int TV_IAPP_RTE_STATE_PREPARING = 2;
+    /**
+     * Ready state of interactive app RTE.
+     * @hide
+     */
+    public static final int TV_IAPP_RTE_STATE_READY = 3;
+    /**
+     * Error state of interactive app RTE.
+     * @hide
+     */
+    public static final int TV_IAPP_RTE_STATE_ERROR = 4;
+
+    /**
+     * Key for package name in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_PACKAGE_NAME = "package_name";
+
+    /**
+     * Key for class name in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_CLASS_NAME = "class_name";
+
+    /**
+     * Key for URI scheme in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_URI_SCHEME = "uri_scheme";
+
+    /**
+     * Key for URI host in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_URI_HOST = "uri_host";
+
+    /**
+     * Key for URI prefix in app link.
+     * <p>Type: String
+     *
+     * @see #notifyAppLinkInfo(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_URI_PREFIX = "uri_prefix";
+
+    /**
+     * Key for command type in app link command.
+     * <p>Type: String
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_COMMAND_TYPE = "command_type";
+
+    /**
+     * Key for service ID in app link command.
+     * <p>Type: String
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_SERVICE_ID = "service_id";
+
+    /**
+     * Key for back URI in app link command.
+     * <p>Type: String
+     *
+     * @see #sendAppLinkCommand(String, Bundle)
+     * @hide
+     */
+    public static final String KEY_BACK_URI = "back_uri";
+
     private final ITvIAppManager mService;
     private final int mUserId;
 
@@ -134,9 +242,33 @@
                     record.postBroadcastInfoRequest(request);
                 }
             }
+
+            @Override
+            public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType,
+                    Bundle parameters, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postCommandRequest(cmdType, parameters);
+                }
+            }
+
+            @Override
+            public void onSessionStateChanged(int state, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postSessionStateChanged(state);
+                }
+            }
         };
         ITvIAppManagerCallback managerCallback = new ITvIAppManagerCallback.Stub() {
-            // TODO: handle IApp service state changes
             @Override
             public void onIAppServiceAdded(String iAppServiceId) {
                 synchronized (mLock) {
@@ -173,6 +305,15 @@
                     }
                 }
             }
+
+            @Override
+            public void onStateChanged(String iAppServiceId, int type, int state) {
+                synchronized (mLock) {
+                    for (TvIAppCallbackRecord record : mCallbackRecords) {
+                        record.postStateChanged(iAppServiceId, type, state);
+                    }
+                }
+            }
         };
         try {
             if (mService != null) {
@@ -233,6 +374,15 @@
          */
         public void onTvIAppInfoUpdated(TvIAppInfo iAppInfo) {
         }
+
+
+        /**
+         * This is called when the state of the interactive app service is changed.
+         * @hide
+         */
+        public void onTvIAppServiceStateChanged(
+                String iAppServiceId, int type, @TvIAppRteState int state) {
+        }
     }
 
     private static final class TvIAppCallbackRecord {
@@ -283,6 +433,15 @@
                 }
             });
         }
+
+        public void postStateChanged(String iAppServiceId, int type, int state) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onTvIAppServiceStateChanged(iAppServiceId, type, state);
+                }
+            });
+        }
     }
 
     /**
@@ -347,6 +506,30 @@
     }
 
     /**
+     * Notifies app link info.
+     * @hide
+     */
+    public void notifyAppLinkInfo(String tvIAppServiceId, Bundle appLinkInfo) {
+        try {
+            mService.notifyAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sends app link command.
+     * @hide
+     */
+    public void sendAppLinkCommand(String tvIAppServiceId, Bundle command) {
+        try {
+            mService.sendAppLinkCommand(tvIAppServiceId, command, mUserId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Registers a {@link TvIAppManager.TvIAppCallback}.
      *
      * @param callback A callback used to monitor status of the TV IApp services.
@@ -875,6 +1058,25 @@
                 }
             });
         }
+
+        void postCommandRequest(final @TvIAppService.IAppServiceCommandType String cmdType,
+                final Bundle parameters) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onCommandRequest(mSession, cmdType, parameters);
+                }
+            });
+        }
+
+        void postSessionStateChanged(int state) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onSessionStateChanged(mSession, state);
+                }
+            });
+        }
     }
 
     /**
@@ -912,5 +1114,25 @@
          */
         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
         }
+
+        /**
+         * This is called when {@link TvIAppService.Session#requestCommand} is called.
+         *
+         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param cmdType type of the command.
+         * @param parameters parameters of the command.
+         */
+        public void onCommandRequest(Session session,
+                @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+        }
+
+        /**
+         * This is called when {@link TvIAppService.Session#notifySessionStateChanged} is called.
+         *
+         * @param session A {@link TvIAppManager.Session} associated with this callback.
+         * @param state the current state.
+         */
+        public void onSessionStateChanged(Session session, int state) {
+        }
     }
 }
diff --git a/media/java/android/media/tv/interactive/TvIAppService.java b/media/java/android/media/tv/interactive/TvIAppService.java
index 027b890..6475f90 100644
--- a/media/java/android/media/tv/interactive/TvIAppService.java
+++ b/media/java/android/media/tv/interactive/TvIAppService.java
@@ -19,6 +19,7 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.Service;
@@ -30,6 +31,7 @@
 import android.media.tv.BroadcastInfoResponse;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -52,6 +54,8 @@
 
 import com.android.internal.os.SomeArgs;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -82,6 +86,28 @@
      */
     public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(prefix = "IAPP_SERVICE_COMMAND_TYPE_", value = {
+            IAPP_SERVICE_COMMAND_TYPE_TUNE,
+            IAPP_SERVICE_COMMAND_TYPE_TUNENEXT,
+            IAPP_SERVICE_COMMAND_TYPE_TUNEPREV,
+            IAPP_SERVICE_COMMAND_TYPE_STOP,
+            IAPP_SERVICE_COMMAND_TYPE_SETSTREAMVOLUME
+    })
+    public @interface IAppServiceCommandType {}
+
+    /** @hide */
+    public static final String IAPP_SERVICE_COMMAND_TYPE_TUNE = "Tune";
+    /** @hide */
+    public static final String IAPP_SERVICE_COMMAND_TYPE_TUNENEXT = "TuneNext";
+    /** @hide */
+    public static final String IAPP_SERVICE_COMMAND_TYPE_TUNEPREV = "TunePrevious";
+    /** @hide */
+    public static final String IAPP_SERVICE_COMMAND_TYPE_STOP = "Stop";
+    /** @hide */
+    public static final String IAPP_SERVICE_COMMAND_TYPE_SETSTREAMVOLUME = "setStreamVolume";
+
     private final Handler mServiceHandler = new ServiceHandler();
     private final RemoteCallbackList<ITvIAppServiceCallback> mCallbacks =
             new RemoteCallbackList<>();
@@ -123,6 +149,16 @@
             public void prepare(int type) {
                 onPrepare(type);
             }
+
+            @Override
+            public void notifyAppLinkInfo(Bundle appLinkInfo) {
+                onAppLinkInfo(appLinkInfo);
+            }
+
+            @Override
+            public void sendAppLinkCommand(Bundle command) {
+                onAppLinkCommand(command);
+            }
         };
         return tvIAppServiceBinder;
     }
@@ -135,6 +171,22 @@
         // TODO: make it abstract when unhide
     }
 
+    /**
+     * Registers App link info.
+     * @hide
+     */
+    public void onAppLinkInfo(Bundle appLinkInfo) {
+        // TODO: make it abstract when unhide
+    }
+
+    /**
+     * Sends App link info.
+     * @hide
+     */
+    public void onAppLinkCommand(Bundle command) {
+        // TODO: make it abstract when unhide
+    }
+
 
     /**
      * Returns a concrete implementation of {@link Session}.
@@ -153,6 +205,16 @@
     }
 
     /**
+     * Notifies the system when the state of the interactive app has been changed.
+     * @param state the current state
+     * @hide
+     */
+    public final void notifyStateChanged(int type, @TvIAppManager.TvIAppRteState int state) {
+        mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_RTE_STATE_CHANGED,
+                type, state).sendToTarget();
+    }
+
+    /**
      * Base class for derived classes to implement to provide a TV interactive app session.
      * @hide
      */
@@ -384,7 +446,7 @@
 
         /**
          * Requests broadcast related information from the related TV input.
-         * @param request
+         * @param request the request for broadcast info
          */
         public void requestBroadcastInfo(@NonNull final BroadcastInfoRequest request) {
             executeOrPostRunnableOnMainThread(new Runnable() {
@@ -406,6 +468,31 @@
             });
         }
 
+        /**
+         * requests a specific command to be processed by the related TV input.
+         * @param cmdType type of the specific command
+         * @param parameters parameters of the specific command
+         */
+        public void requestCommand(@IAppServiceCommandType String cmdType, Bundle parameters) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "requestCommand (cmdType=" + cmdType + ", parameters="
+                                    + parameters.toString() + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onCommandRequest(cmdType, parameters);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in requestCommand", e);
+                    }
+                }
+            });
+        }
+
         void startIApp() {
             onStartIApp();
         }
@@ -445,6 +532,30 @@
         }
 
         /**
+         * Notifies when the session state is changed.
+         * @param state the current state.
+         */
+        public void notifySessionStateChanged(@TvIAppManager.TvIAppRteState int state) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG, "notifySessionStateChanged (state="
+                                    + state + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onSessionStateChanged(state);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifySessionStateChanged", e);
+                    }
+                }
+            });
+        }
+
+        /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
@@ -747,6 +858,19 @@
     private final class ServiceHandler extends Handler {
         private static final int DO_CREATE_SESSION = 1;
         private static final int DO_NOTIFY_SESSION_CREATED = 2;
+        private static final int DO_NOTIFY_RTE_STATE_CHANGED = 3;
+
+        private void broadcastRteStateChanged(int type, int state) {
+            int n = mCallbacks.beginBroadcast();
+            for (int i = 0; i < n; ++i) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onStateChanged(type, state);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "error in broadcastRteStateChanged", e);
+                }
+            }
+            mCallbacks.finishBroadcast();
+        }
 
         @Override
         public void handleMessage(Message msg) {
@@ -795,6 +919,12 @@
                     args.recycle();
                     return;
                 }
+                case DO_NOTIFY_RTE_STATE_CHANGED: {
+                    int type = msg.arg1;
+                    int state = msg.arg2;
+                    broadcastRteStateChanged(type, state);
+                    return;
+                }
                 default: {
                     Log.w(TAG, "Unhandled message code: " + msg.what);
                     return;
diff --git a/media/java/android/media/tv/interactive/TvIAppView.java b/media/java/android/media/tv/interactive/TvIAppView.java
index 8031981..efbe9e3 100644
--- a/media/java/android/media/tv/interactive/TvIAppView.java
+++ b/media/java/android/media/tv/interactive/TvIAppView.java
@@ -26,6 +26,7 @@
 import android.media.tv.TvView;
 import android.media.tv.interactive.TvIAppManager.Session;
 import android.media.tv.interactive.TvIAppManager.SessionCallback;
+import android.os.Bundle;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -53,6 +54,7 @@
     private final Handler mHandler = new Handler();
     private Session mSession;
     private MySessionCallback mSessionCallback;
+    private TvIAppCallback mCallback;
     private SurfaceView mSurfaceView;
     private Surface mSurface;
 
@@ -123,6 +125,16 @@
         mTvIAppManager = (TvIAppManager) getContext().getSystemService("tv_interactive_app");
     }
 
+    /**
+     * Sets the callback to be invoked when an event is dispatched to this TvIAppView.
+     *
+     * @param callback The callback to receive events. A value of {@code null} removes the existing
+     *            callback.
+     */
+    public void setCallback(@Nullable TvIAppCallback callback) {
+        mCallback = callback;
+    }
+
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -322,6 +334,23 @@
         return UNSET_TVVIEW_SUCCESS;
     }
 
+    /**
+     * Callback used to receive various status updates on the {@link TvIAppView}.
+     */
+    public abstract static class TvIAppCallback {
+
+        /**
+         * This is called when a command is requested to be processed by the related TV input.
+         *
+         * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+         * @param cmdType type of the command
+         * @param parameters parameters of the command
+         */
+        public void onCommandRequest(String iAppServiceId,
+                @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+        }
+    }
+
     private class MySessionCallback extends SessionCallback {
         final String mIAppServiceId;
         int mType;
@@ -395,5 +424,21 @@
             mUseRequestedSurfaceLayout = true;
             requestLayout();
         }
+
+        @Override
+        public void onCommandRequest(Session session,
+                @TvIAppService.IAppServiceCommandType String cmdType, Bundle parameters) {
+            if (DEBUG) {
+                Log.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
+                        + parameters.toString() + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onCommandRequest - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onCommandRequest(mIAppServiceId, cmdType, parameters);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 73a821e..255b391b 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -74,6 +74,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * This class is used to interact with hardware tuners devices.
@@ -248,6 +249,7 @@
 
     private static final int FILTER_CLEANUP_THRESHOLD = 256;
 
+
     /** @hide */
     @IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
     @Retention(RetentionPolicy.SOURCE)
@@ -283,7 +285,7 @@
     @Nullable
     private FrontendInfo mFrontendInfo;
     private Integer mFrontendHandle;
-    private Boolean mIsSharedFrontend = false;
+    private Tuner mFeOwnerTuner = null;
     private int mFrontendType = FrontendSettings.TYPE_UNDEFINED;
     private int mUserId;
     private Lnb mLnb;
@@ -304,6 +306,11 @@
     private final Object mOnTuneEventLock = new Object();
     private final Object mScanCallbackLock = new Object();
     private final Object mOnResourceLostListenerLock = new Object();
+    private final ReentrantLock mFrontendLock = new ReentrantLock();
+    private final ReentrantLock mLnbLock = new ReentrantLock();
+    private final ReentrantLock mFrontendCiCamLock = new ReentrantLock();
+    private final ReentrantLock mDemuxLock = new ReentrantLock();
+    private int mRequestedCiCamId;
 
     private Integer mDemuxHandle;
     private Integer mFrontendCiCamHandle;
@@ -391,7 +398,12 @@
 
     /** @hide */
     public List<Integer> getFrontendIds() {
-        return nativeGetFrontendIds();
+        mFrontendLock.lock();
+        try {
+            return nativeGetFrontendIds();
+        } finally {
+            mFrontendLock.unlock();
+        }
     }
 
     /**
@@ -426,13 +438,19 @@
      * @param tuner the Tuner instance to share frontend resource with.
      */
     public void shareFrontendFromTuner(@NonNull Tuner tuner) {
-        mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
-        synchronized (mIsSharedFrontend) {
-            mFrontendHandle = tuner.mFrontendHandle;
-            mFrontend = tuner.mFrontend;
-            mIsSharedFrontend = true;
+        acquireTRMSLock("shareFrontendFromTuner()");
+        mFrontendLock.lock();
+        try {
+            mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
+            mFeOwnerTuner = tuner;
+            mFeOwnerTuner.registerFrontendCallbackListener(this);
+            mFrontendHandle = mFeOwnerTuner.mFrontendHandle;
+            mFrontend = mFeOwnerTuner.mFrontend;
+            nativeShareFrontend(mFrontend.mId);
+        } finally {
+            releaseTRMSLock();
+            mFrontendLock.unlock();
         }
-        nativeShareFrontend(mFrontend.mId);
     }
 
     /**
@@ -494,43 +512,94 @@
     private long mNativeContext; // used by native jMediaTuner
 
     /**
+     * Registers a tuner as a listener for frontend callbacks.
+     */
+    private void registerFrontendCallbackListener(Tuner tuner) {
+        nativeRegisterFeCbListener(tuner.getNativeContext());
+    }
+
+    /**
+     * Unregisters a tuner as a listener for frontend callbacks.
+     */
+    private void unregisterFrontendCallbackListener(Tuner tuner) {
+        nativeUnregisterFeCbListener(tuner.getNativeContext());
+    }
+
+    /**
+     * Returns the pointer to the associated JTuner.
+     */
+    long getNativeContext() {
+        return mNativeContext;
+    }
+
+    /**
      * Releases the Tuner instance.
      */
     @Override
     public void close() {
-        releaseAll();
-        TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+        acquireTRMSLock("close()");
+        try {
+            releaseAll();
+            TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+        } finally {
+            releaseTRMSLock();
+        }
     }
 
-    private void releaseAll() {
-        if (mFrontendHandle != null) {
-            synchronized (mIsSharedFrontend) {
-                if (!mIsSharedFrontend) {
+    private void releaseFrontend() {
+        mFrontendLock.lock();
+        try {
+            if (mFrontendHandle != null) {
+                if (mFeOwnerTuner != null) {
+                    // unregister self from the Frontend callback
+                    mFeOwnerTuner.unregisterFrontendCallbackListener(this);
+                    mFeOwnerTuner = null;
+                } else {
+                    // close resource as owner
                     int res = nativeCloseFrontend(mFrontendHandle);
                     if (res != Tuner.RESULT_SUCCESS) {
                         TunerUtils.throwExceptionForResult(res, "failed to close frontend");
                     }
                     mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
                 }
-                mIsSharedFrontend = false;
+                FrameworkStatsLog
+                        .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
+                mFrontendHandle = null;
+                mFrontend = null;
             }
-            FrameworkStatsLog
-                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
-                    FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
-            mFrontendHandle = null;
-            mFrontend = null;
+        } finally {
+            mFrontendLock.unlock();
         }
-        if (mLnb != null) {
-            mLnb.close();
-        }
-        if (mFrontendCiCamHandle != null) {
-            int result = nativeUnlinkCiCam(mFrontendCiCamId);
-            if (result == RESULT_SUCCESS) {
-                mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
-                mFrontendCiCamId = null;
-                mFrontendCiCamHandle = null;
+    }
+
+    private void releaseAll() {
+        releaseFrontend();
+
+        mLnbLock.lock();
+        try {
+            // mLnb will be non-null only for owner tuner
+            if (mLnb != null) {
+                mLnb.close();
             }
+        } finally {
+            mLnbLock.unlock();
         }
+
+        mFrontendCiCamLock.lock();
+        try {
+            if (mFrontendCiCamHandle != null) {
+                int result = nativeUnlinkCiCam(mFrontendCiCamId);
+                if (result == RESULT_SUCCESS) {
+                    mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+                    mFrontendCiCamId = null;
+                    mFrontendCiCamHandle = null;
+                }
+            }
+        } finally {
+            mFrontendCiCamLock.unlock();
+        }
+
         synchronized (mDescramblers) {
             if (!mDescramblers.isEmpty()) {
                 for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
@@ -543,6 +612,7 @@
                 mDescramblers.clear();
             }
         }
+
         synchronized (mFilters) {
             if (!mFilters.isEmpty()) {
                 for (WeakReference<Filter> weakFilter : mFilters) {
@@ -554,13 +624,19 @@
                 mFilters.clear();
             }
         }
-        if (mDemuxHandle != null) {
-            int res = nativeCloseDemux(mDemuxHandle);
-            if (res != Tuner.RESULT_SUCCESS) {
-                TunerUtils.throwExceptionForResult(res, "failed to close demux");
+
+        mDemuxLock.lock();
+        try {
+            if (mDemuxHandle != null) {
+                int res = nativeCloseDemux(mDemuxHandle);
+                if (res != Tuner.RESULT_SUCCESS) {
+                    TunerUtils.throwExceptionForResult(res, "failed to close demux");
+                }
+                mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
+                mDemuxHandle = null;
             }
-            mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
-            mDemuxHandle = null;
+        } finally {
+            mDemuxLock.unlock();
         }
 
         mTunerResourceManager.unregisterClientProfile(mClientId);
@@ -592,6 +668,8 @@
      */
     private native Frontend nativeOpenFrontendByHandle(int handle);
     private native int nativeShareFrontend(int id);
+    private native void nativeRegisterFeCbListener(long nativeContext);
+    private native void nativeUnregisterFeCbListener(long nativeContext);
     @Result
     private native int nativeTune(int type, FrontendSettings settings);
     private native int nativeStopTune();
@@ -763,28 +841,37 @@
      */
     @Result
     public int tune(@NonNull FrontendSettings settings) {
-        final int type = settings.getType();
-        if (mFrontendHandle != null && type != mFrontendType) {
-            Log.e(TAG, "Frontend was opened with type " + mFrontendType + ", new type is " + type);
-            return RESULT_INVALID_STATE;
-        }
-        Log.d(TAG, "Tune to " + settings.getFrequencyLong());
-        mFrontendType = type;
-        if (mFrontendType == FrontendSettings.TYPE_DTMB) {
-            if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
-                    TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
+        mFrontendLock.lock();
+        try {
+            final int type = settings.getType();
+            if (mFrontendHandle != null && type != mFrontendType) {
+                Log.e(TAG, "Frontend was opened with type " + mFrontendType
+                        + ", new type is " + type);
+                return RESULT_INVALID_STATE;
+            }
+            Log.d(TAG, "Tune to " + settings.getFrequencyLong());
+            mFrontendType = type;
+            if (mFrontendType == FrontendSettings.TYPE_DTMB) {
+                if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
+                    return RESULT_UNAVAILABLE;
+                }
+            }
+
+            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+                mFrontendInfo = null;
+                Log.d(TAG, "Write Stats Log for tuning.");
+                FrameworkStatsLog
+                        .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                            FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
+                int res = nativeTune(settings.getType(), settings);
+                return res;
+            } else {
                 return RESULT_UNAVAILABLE;
             }
+        } finally {
+            mFrontendLock.unlock();
         }
-        if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
-            mFrontendInfo = null;
-            Log.d(TAG, "Write Stats Log for tuning.");
-            FrameworkStatsLog
-                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
-                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
-            return nativeTune(settings.getType(), settings);
-        }
-        return RESULT_UNAVAILABLE;
     }
 
     /**
@@ -797,7 +884,12 @@
      */
     @Result
     public int cancelTuning() {
-        return nativeStopTune();
+        mFrontendLock.lock();
+        try {
+            return nativeStopTune();
+        } finally {
+            mFrontendLock.unlock();
+        }
     }
 
     /**
@@ -824,33 +916,41 @@
     @Result
     public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
             @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
-        synchronized (mScanCallbackLock) {
-            // Scan can be called again for blink scan if scanCallback and executor are same as
-            //before.
-            if (((mScanCallback != null) && (mScanCallback != scanCallback))
-                || ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) {
-                throw new IllegalStateException(
-                    "Different Scan session already in progress.  stopScan must be called "
-                        + "before a new scan session can be " + "started.");
-            }
-            mFrontendType = settings.getType();
-            if (mFrontendType == FrontendSettings.TYPE_DTMB) {
-                if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
-                        TunerVersionChecker.TUNER_VERSION_1_1,
-                        "Scan with DTMB Frontend")) {
-                    return RESULT_UNAVAILABLE;
+
+        mFrontendLock.lock();
+        try {
+            synchronized (mScanCallbackLock) {
+                // Scan can be called again for blink scan if scanCallback and executor are same as
+                //before.
+                if (((mScanCallback != null) && (mScanCallback != scanCallback))
+                        || ((mScanCallbackExecutor != null)
+                            && (mScanCallbackExecutor != executor))) {
+                    throw new IllegalStateException(
+                        "Different Scan session already in progress.  stopScan must be called "
+                            + "before a new scan session can be " + "started.");
                 }
+                mFrontendType = settings.getType();
+                if (mFrontendType == FrontendSettings.TYPE_DTMB) {
+                    if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+                            TunerVersionChecker.TUNER_VERSION_1_1,
+                            "Scan with DTMB Frontend")) {
+                        return RESULT_UNAVAILABLE;
+                    }
+                }
+                if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+                          mFrontendLock)) {
+                    mScanCallback = scanCallback;
+                    mScanCallbackExecutor = executor;
+                    mFrontendInfo = null;
+                    FrameworkStatsLog
+                            .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                            FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
+                    return nativeScan(settings.getType(), settings, scanType);
+                }
+                return RESULT_UNAVAILABLE;
             }
-            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
-                mScanCallback = scanCallback;
-                mScanCallbackExecutor = executor;
-                mFrontendInfo = null;
-                FrameworkStatsLog
-                    .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
-                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
-                return nativeScan(settings.getType(), settings, scanType);
-            }
-            return RESULT_UNAVAILABLE;
+        } finally {
+            mFrontendLock.unlock();
         }
     }
 
@@ -867,14 +967,19 @@
      */
     @Result
     public int cancelScanning() {
-        synchronized (mScanCallbackLock) {
-            FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
-                    FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
+        mFrontendLock.lock();
+        try {
+            synchronized (mScanCallbackLock) {
+                FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+                        FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
 
-            int retVal = nativeStopScan();
-            mScanCallback = null;
-            mScanCallbackExecutor = null;
-            return retVal;
+                int retVal = nativeStopScan();
+                mScanCallback = null;
+                mScanCallbackExecutor = null;
+                return retVal;
+            }
+        } finally {
+            mFrontendLock.unlock();
         }
     }
 
@@ -903,7 +1008,12 @@
      */
     @Result
     private int setLnb(@NonNull Lnb lnb) {
-        return nativeSetLnb(lnb);
+        mLnbLock.lock();
+        try {
+            return nativeSetLnb(lnb);
+        } finally {
+            mLnbLock.unlock();
+        }
     }
 
     /**
@@ -929,10 +1039,15 @@
      */
     @Nullable
     public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
-        if (mFrontend == null) {
-            throw new IllegalStateException("frontend is not initialized");
+        mFrontendLock.lock();
+        try {
+            if (mFrontend == null) {
+                throw new IllegalStateException("frontend is not initialized");
+            }
+            return nativeGetFrontendStatus(statusTypes);
+        } finally {
+            mFrontendLock.unlock();
         }
-        return nativeGetFrontendStatus(statusTypes);
     }
 
     /**
@@ -942,11 +1057,16 @@
      * @return the id of hardware A/V sync.
      */
     public int getAvSyncHwId(@NonNull Filter filter) {
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return INVALID_AV_SYNC_ID;
+        mDemuxLock.lock();
+        try {
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return INVALID_AV_SYNC_ID;
+            }
+            Integer id = nativeGetAvSyncHwId(filter);
+            return id == null ? INVALID_AV_SYNC_ID : id;
+        } finally {
+            mDemuxLock.unlock();
         }
-        Integer id = nativeGetAvSyncHwId(filter);
-        return id == null ? INVALID_AV_SYNC_ID : id;
     }
 
     /**
@@ -959,11 +1079,16 @@
      * @return the current timestamp of hardware A/V sync.
      */
     public long getAvSyncTime(int avSyncHwId) {
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return INVALID_TIMESTAMP;
+        mDemuxLock.lock();
+        try {
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return INVALID_TIMESTAMP;
+            }
+            Long time = nativeGetAvSyncTime(avSyncHwId);
+            return time == null ? INVALID_TIMESTAMP : time;
+        } finally {
+            mDemuxLock.unlock();
         }
-        Long time = nativeGetAvSyncTime(avSyncHwId);
-        return time == null ? INVALID_TIMESTAMP : time;
     }
 
     /**
@@ -980,10 +1105,15 @@
      */
     @Result
     public int connectCiCam(int ciCamId) {
-        if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return nativeConnectCiCam(ciCamId);
+        mDemuxLock.lock();
+        try {
+            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return nativeConnectCiCam(ciCamId);
+            }
+            return RESULT_UNAVAILABLE;
+        } finally {
+            mDemuxLock.unlock();
         }
-        return RESULT_UNAVAILABLE;
     }
 
     /**
@@ -1011,14 +1141,30 @@
      *         {@link TunerVersionChecker#getTunerVersion()}.
      */
     public int connectFrontendToCiCam(int ciCamId) {
-        if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
-                "linkFrontendToCiCam")) {
-            if (checkCiCamResource(ciCamId)
-                    && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
-                return nativeLinkCiCam(ciCamId);
+        // TODO: change this so TRMS lock is held only when the resource handles for
+        // CiCam/Frontend is null. Current implementation can only handle one local lock for that.
+        acquireTRMSLock("connectFrontendToCiCam()");
+        mFrontendCiCamLock.lock();
+        mFrontendLock.lock();
+        try {
+            if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+                    TunerVersionChecker.TUNER_VERSION_1_1,
+                    "linkFrontendToCiCam")) {
+                mRequestedCiCamId = ciCamId;
+                // No need to unlock mFrontendCiCamLock and mFrontendLock below becauase
+                // TRMS lock is already acquired. Pass null to disable lock related operations
+                if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, null)
+                        && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, null)
+                    ) {
+                    return nativeLinkCiCam(ciCamId);
+                }
             }
+            return INVALID_LTS_ID;
+        } finally {
+            releaseTRMSLock();
+            mFrontendCiCamLock.unlock();
+            mFrontendLock.unlock();
         }
-        return INVALID_LTS_ID;
     }
 
     /**
@@ -1033,10 +1179,15 @@
      */
     @Result
     public int disconnectCiCam() {
-        if (mDemuxHandle != null) {
-            return nativeDisconnectCiCam();
+        mDemuxLock.lock();
+        try {
+            if (mDemuxHandle != null) {
+                return nativeDisconnectCiCam();
+            }
+            return RESULT_UNAVAILABLE;
+        } finally {
+            mDemuxLock.unlock();
         }
-        return RESULT_UNAVAILABLE;
     }
 
     /**
@@ -1057,20 +1208,30 @@
      */
     @Result
     public int disconnectFrontendToCiCam(int ciCamId) {
-        if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
-                "unlinkFrontendToCiCam")) {
-            if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
-                    && mFrontendCiCamId == ciCamId) {
-                int result = nativeUnlinkCiCam(ciCamId);
-                if (result == RESULT_SUCCESS) {
-                    mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
-                    mFrontendCiCamId = null;
-                    mFrontendCiCamHandle = null;
+        acquireTRMSLock("disconnectFrontendToCiCam()");
+        try {
+            if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+                    TunerVersionChecker.TUNER_VERSION_1_1,
+                    "unlinkFrontendToCiCam")) {
+                mFrontendCiCamLock.lock();
+                if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
+                        && mFrontendCiCamId == ciCamId) {
+                    int result = nativeUnlinkCiCam(ciCamId);
+                    if (result == RESULT_SUCCESS) {
+                        mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+                        mFrontendCiCamId = null;
+                        mFrontendCiCamHandle = null;
+                    }
+                    return result;
                 }
-                return result;
             }
+            return RESULT_UNAVAILABLE;
+        } finally {
+            if (mFrontendCiCamLock.isLocked()) {
+                mFrontendCiCamLock.unlock();
+            }
+            releaseTRMSLock();
         }
-        return RESULT_UNAVAILABLE;
     }
 
     /**
@@ -1082,16 +1243,21 @@
      */
     @Nullable
     public FrontendInfo getFrontendInfo() {
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
-            return null;
+        mFrontendLock.lock();
+        try {
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+                return null;
+            }
+            if (mFrontend == null) {
+                throw new IllegalStateException("frontend is not initialized");
+            }
+            if (mFrontendInfo == null) {
+                mFrontendInfo = getFrontendInfoById(mFrontend.mId);
+            }
+            return mFrontendInfo;
+        } finally {
+            mFrontendLock.unlock();
         }
-        if (mFrontend == null) {
-            throw new IllegalStateException("frontend is not initialized");
-        }
-        if (mFrontendInfo == null) {
-            mFrontendInfo = getFrontendInfoById(mFrontend.mId);
-        }
-        return mFrontendInfo;
     }
 
     /**
@@ -1114,7 +1280,12 @@
 
     /** @hide */
     public FrontendInfo getFrontendInfoById(int id) {
-        return nativeGetFrontendInfo(id);
+        mFrontendLock.lock();
+        try {
+            return nativeGetFrontendInfo(id);
+        } finally {
+            mFrontendLock.unlock();
+        }
     }
 
     /**
@@ -1125,11 +1296,16 @@
      */
     @Nullable
     public DemuxCapabilities getDemuxCapabilities() {
-        return nativeGetDemuxCapabilities();
+        mDemuxLock.lock();
+        try {
+            return nativeGetDemuxCapabilities();
+        } finally {
+            mDemuxLock.unlock();
+        }
     }
 
     private void onFrontendEvent(int eventType) {
-        Log.d(TAG, "Got event from tuning. Event type: " + eventType);
+        Log.d(TAG, "Got event from tuning. Event type: " + eventType + " for " + this);
         synchronized (mOnTuneEventLock) {
             if (mOnTuneEventExecutor != null && mOnTuneEventListener != null) {
                 mOnTuneEventExecutor.execute(() -> {
@@ -1417,32 +1593,37 @@
     public Filter openFilter(@Type int mainType, @Subtype int subType,
             @BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
             @Nullable FilterCallback cb) {
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return null;
-        }
-        Filter filter = nativeOpenFilter(
-                mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
-        if (filter != null) {
-            filter.setType(mainType, subType);
-            filter.setCallback(cb, executor);
-            if (mHandler == null) {
-                mHandler = createEventHandler();
+        mDemuxLock.lock();
+        try {
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return null;
             }
-            synchronized (mFilters) {
-                WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
-                mFilters.add(weakFilter);
-                if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
-                    Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
-                    while (iterator.hasNext()) {
-                        WeakReference<Filter> wFilter = iterator.next();
-                        if (wFilter.get() == null) {
-                            iterator.remove();
+            Filter filter = nativeOpenFilter(
+                    mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
+            if (filter != null) {
+                filter.setType(mainType, subType);
+                filter.setCallback(cb, executor);
+                if (mHandler == null) {
+                    mHandler = createEventHandler();
+                }
+                synchronized (mFilters) {
+                    WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
+                    mFilters.add(weakFilter);
+                    if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
+                        Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
+                        while (iterator.hasNext()) {
+                            WeakReference<Filter> wFilter = iterator.next();
+                            if (wFilter.get() == null) {
+                                iterator.remove();
+                            }
                         }
                     }
                 }
             }
+            return filter;
+        } finally {
+            mDemuxLock.unlock();
         }
-        return filter;
     }
 
     /**
@@ -1457,18 +1638,24 @@
      */
     @Nullable
     public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
-        Objects.requireNonNull(executor, "executor must not be null");
-        Objects.requireNonNull(cb, "LnbCallback must not be null");
-        if (mLnb != null) {
-            mLnb.setCallback(executor, cb, this);
-            return mLnb;
+        mLnbLock.lock();
+        try {
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(cb, "LnbCallback must not be null");
+            if (mLnb != null) {
+                mLnb.setCallback(executor, cb, this);
+                return mLnb;
+            }
+            if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
+                    && mLnb != null) {
+                mLnb.setCallback(executor, cb, this);
+                setLnb(mLnb);
+                return mLnb;
+            }
+            return null;
+        } finally {
+            mLnbLock.unlock();
         }
-        if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
-            mLnb.setCallback(executor, cb, this);
-            setLnb(mLnb);
-            return mLnb;
-        }
-        return null;
     }
 
     /**
@@ -1483,20 +1670,25 @@
     @Nullable
     public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
             @NonNull LnbCallback cb) {
-        Objects.requireNonNull(name, "LNB name must not be null");
-        Objects.requireNonNull(executor, "executor must not be null");
-        Objects.requireNonNull(cb, "LnbCallback must not be null");
-        Lnb newLnb = nativeOpenLnbByName(name);
-        if (newLnb != null) {
-            if (mLnb != null) {
-                mLnb.close();
-                mLnbHandle = null;
+        mLnbLock.lock();
+        try {
+            Objects.requireNonNull(name, "LNB name must not be null");
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(cb, "LnbCallback must not be null");
+            Lnb newLnb = nativeOpenLnbByName(name);
+            if (newLnb != null) {
+                if (mLnb != null) {
+                    mLnb.close();
+                    mLnbHandle = null;
+                }
+                mLnb = newLnb;
+                mLnb.setCallback(executor, cb, this);
+                setLnb(mLnb);
             }
-            mLnb = newLnb;
-            mLnb.setCallback(executor, cb, this);
-            setLnb(mLnb);
+            return mLnb;
+        } finally {
+            mLnbLock.unlock();
         }
-        return mLnb;
     }
 
     private boolean requestLnb() {
@@ -1518,10 +1710,15 @@
      */
     @Nullable
     public TimeFilter openTimeFilter() {
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return null;
+        mDemuxLock.lock();
+        try {
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return null;
+            }
+            return nativeOpenTimeFilter();
+        } finally {
+            mDemuxLock.unlock();
         }
-        return nativeOpenTimeFilter();
     }
 
     /**
@@ -1532,10 +1729,15 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
     @Nullable
     public Descrambler openDescrambler() {
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return null;
+        mDemuxLock.lock();
+        try {
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return null;
+            }
+            return requestDescrambler();
+        } finally {
+            mDemuxLock.unlock();
         }
-        return requestDescrambler();
     }
 
     /**
@@ -1553,14 +1755,19 @@
             @BytesLong long bufferSize,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OnRecordStatusChangedListener l) {
-        Objects.requireNonNull(executor, "executor must not be null");
-        Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return null;
+        mDemuxLock.lock();
+        try {
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return null;
+            }
+            DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
+            dvr.setListener(executor, l);
+            return dvr;
+        } finally {
+            mDemuxLock.unlock();
         }
-        DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
-        dvr.setListener(executor, l);
-        return dvr;
     }
 
     /**
@@ -1578,14 +1785,19 @@
             @BytesLong long bufferSize,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OnPlaybackStatusChangedListener l) {
-        Objects.requireNonNull(executor, "executor must not be null");
-        Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
-        if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
-            return null;
+        mDemuxLock.lock();
+        try {
+            Objects.requireNonNull(executor, "executor must not be null");
+            Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
+            if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+                return null;
+            }
+            DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
+            dvr.setListener(executor, l);
+            return dvr;
+        } finally {
+            mDemuxLock.unlock();
         }
-        DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
-        dvr.setListener(executor, l);
-        return dvr;
     }
 
     /**
@@ -1602,6 +1814,8 @@
     static public SharedFilter openSharedFilter(@NonNull Context context,
             @NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
             @NonNull SharedFilterCallback cb) {
+        // TODO: check what happenes when onReclaimResources() is called and see if
+        // this needs to be protected with TRMS lock
         Objects.requireNonNull(sharedFilterToken, "sharedFilterToken must not be null");
         Objects.requireNonNull(executor, "executor must not be null");
         Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
@@ -1665,22 +1879,28 @@
         return granted;
     }
 
-    private boolean checkResource(int resourceType)  {
+    private boolean checkResource(int resourceType, ReentrantLock localLock)  {
         switch (resourceType) {
             case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
-                if (mFrontendHandle == null && !requestFrontend()) {
+                if (mFrontendHandle == null && !requestResource(resourceType, localLock)) {
                     return false;
                 }
                 break;
             }
             case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
-                if (mLnb == null && !requestLnb()) {
+                if (mLnb == null && !requestResource(resourceType, localLock)) {
                     return false;
                 }
                 break;
             }
             case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
-                if (mDemuxHandle == null && !requestDemux()) {
+                if (mDemuxHandle == null && !requestResource(resourceType, localLock)) {
+                    return false;
+                }
+                break;
+            }
+            case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
+                if (mFrontendCiCamHandle == null && !requestResource(resourceType, localLock)) {
                     return false;
                 }
                 break;
@@ -1691,24 +1911,91 @@
         return true;
     }
 
-    private boolean checkCiCamResource(int ciCamId) {
-        if (mFrontendCiCamHandle == null && !requestFrontendCiCam(ciCamId)) {
-            return false;
+    // Expected flow of how to use this function is:
+    // 1) lock the localLock and check if the resource is already held
+    // 2) if yes, no need to call this function and continue with the handle with the lock held
+    // 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock
+    // 4) grab the local lock again and release the TRMS lock
+    // If localLock is null, we'll assume the caller does not want the lock related operations
+    private boolean requestResource(int resourceType, ReentrantLock localLock)  {
+        boolean enableLockOperations = localLock != null;
+
+        // release the local lock first to avoid deadlock
+        if (enableLockOperations) {
+            if (localLock.isLocked()) {
+                localLock.unlock();
+            } else {
+                throw new IllegalStateException("local lock must be locked beforehand");
+            }
         }
-        return true;
+
+        // now safe to grab TRMS lock
+        if (enableLockOperations) {
+            acquireTRMSLock("requestResource:" + resourceType);
+        }
+
+        try {
+            // lock the local lock
+            if (enableLockOperations) {
+                localLock.lock();
+            }
+            switch (resourceType) {
+                case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
+                    return requestFrontend();
+                }
+                case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
+                    return requestLnb();
+                }
+                case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
+                    return requestDemux();
+                }
+                case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
+                    return requestFrontendCiCam(mRequestedCiCamId);
+                }
+                default:
+                    return false;
+            }
+        } finally {
+            if (enableLockOperations) {
+                releaseTRMSLock();
+            }
+        }
     }
 
     /* package */ void releaseLnb() {
-        if (mLnbHandle != null) {
-            // LNB handle can be null if it's opened by name.
-            mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
-            mLnbHandle = null;
+        acquireTRMSLock("releaseLnb()");
+        mLnbLock.lock();
+        try {
+            if (mLnbHandle != null) {
+                // LNB handle can be null if it's opened by name.
+                mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
+                mLnbHandle = null;
+            }
+            mLnb = null;
+        } finally {
+            releaseTRMSLock();
+            mLnbLock.unlock();
         }
-        mLnb = null;
     }
 
     /** @hide */
     public int getClientId() {
         return mClientId;
     }
+
+    private void acquireTRMSLock(String functionNameForLog) {
+        if (DEBUG) {
+            Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog
+                    + "for clientId:" + mClientId);
+        }
+        if (!mTunerResourceManager.acquireLock(mClientId)) {
+            Log.e(TAG, "FAILED:acquireLock() in " + functionNameForLog
+                    + " for clientId:" + mClientId + " - this can cause deadlock between"
+                    + " Tuner API calls and onReclaimResources()");
+        }
+    }
+
+    private void releaseTRMSLock() {
+        mTunerResourceManager.releaseLock(mClientId);
+    }
 }
diff --git a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
index cfd8583..6f3ab03 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrPlayback.java
@@ -98,6 +98,7 @@
     private native void nativeSetFileDescriptor(int fd);
     private native long nativeRead(long size);
     private native long nativeRead(byte[] bytes, long offset, long size);
+    private native long nativeSeek(long pos);
 
     private DvrPlayback() {
         mUserId = Process.myUid();
@@ -243,7 +244,7 @@
      *
      * @param fd the file descriptor to read data.
      * @see #read(long)
-     * @see #read(byte[], long, long)
+     * @see #seek(long)
      */
     public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
         nativeSetFileDescriptor(fd.getFd());
@@ -261,19 +262,30 @@
     }
 
     /**
-     * Reads data from the buffer for DVR playback and copies to the given byte array.
+     * Reads data from the buffer for DVR playback.
      *
-     * @param bytes the byte array to store the data.
-     * @param offset the index of the first byte in {@code bytes} to copy to.
+     * @param buffer the byte array where DVR reads data from.
+     * @param offset the index of the first byte in {@code buffer} to read.
      * @param size the maximum number of bytes to read.
      * @return the number of bytes read.
      */
     @BytesLong
-    public long read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
-        if (size + offset > bytes.length) {
+    public long read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > buffer.length) {
             throw new ArrayIndexOutOfBoundsException(
-                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+                    "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size);
         }
-        return nativeRead(bytes, offset, size);
+        return nativeRead(buffer, offset, size);
+    }
+
+    /**
+     * Sets the file pointer offset of the file descriptor.
+     *
+     * @param pos the offset position, measured in bytes from the beginning of the file.
+     * @return the new offset position.
+     */
+    @BytesLong
+    public long seek(@BytesLong long pos) {
+        return nativeSeek(pos);
     }
 }
diff --git a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
index 212a713..e72026a 100644
--- a/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
+++ b/media/java/android/media/tv/tuner/dvr/DvrRecorder.java
@@ -216,7 +216,6 @@
      *
      * @param fd the file descriptor to write data.
      * @see #write(long)
-     * @see #write(byte[], long, long)
      */
     public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
         nativeSetFileDescriptor(fd.getFd());
@@ -236,17 +235,17 @@
     /**
      * Writes recording data to buffer.
      *
-     * @param bytes the byte array stores the data to be written to DVR.
-     * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+     * @param buffer the byte array stores the data from DVR.
+     * @param offset the index of the first byte in {@code buffer} to write the data from DVR.
      * @param size the maximum number of bytes to write.
      * @return the number of bytes written.
      */
     @BytesLong
-    public long write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
-        if (size + offset > bytes.length) {
+    public long write(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+        if (size + offset > buffer.length) {
             throw new ArrayIndexOutOfBoundsException(
-                    "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+                    "Array length=" + buffer.length + ", offset=" + offset + ", size=" + size);
         }
-        return nativeWrite(bytes, offset, size);
+        return nativeWrite(buffer, offset, size);
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
index 394211be..25989db 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadEvent.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -27,15 +27,17 @@
 @SystemApi
 public class DownloadEvent extends FilterEvent {
     private final int mItemId;
+    private final int mDownloadId;
     private final int mMpuSequenceNumber;
     private final int mItemFragmentIndex;
     private final int mLastItemFragmentIndex;
     private final int mDataLength;
 
     // This constructor is used by JNI code only
-    private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+    private DownloadEvent(int itemId, int downloadId, int mpuSequenceNumber, int itemFragmentIndex,
             int lastItemFragmentIndex, int dataLength) {
         mItemId = itemId;
+        mDownloadId = downloadId;
         mMpuSequenceNumber = mpuSequenceNumber;
         mItemFragmentIndex = itemFragmentIndex;
         mLastItemFragmentIndex = lastItemFragmentIndex;
@@ -50,6 +52,15 @@
     }
 
     /**
+     * Gets download ID.
+     *
+     * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code -1}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     */
+    public int getDownloadId() { return mDownloadId; }
+
+    /**
      * Gets MPU sequence number of filtered data.
      */
     @IntRange(from = 0)
@@ -80,4 +91,3 @@
         return mDataLength;
     }
 }
-
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
index 7ba923e..e2cfd7c 100644
--- a/media/java/android/media/tv/tuner/filter/DownloadSettings.java
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.TunerVersionChecker;
 
 /**
  * Filter Settings for a Download.
@@ -27,10 +28,12 @@
  */
 @SystemApi
 public class DownloadSettings extends Settings {
+    private final boolean mUseDownloadId;
     private final int mDownloadId;
 
-    private DownloadSettings(int mainType, int downloadId) {
+    private DownloadSettings(int mainType, boolean useDownloadId, int downloadId) {
         super(TunerUtils.getFilterSubtype(mainType, Filter.SUBTYPE_DOWNLOAD));
+        mUseDownloadId = useDownloadId;
         mDownloadId = downloadId;
     }
 
@@ -42,6 +45,15 @@
     }
 
     /**
+     * Gets whether download ID is used.
+     *
+     * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code false}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     */
+    public boolean useDownloadId() { return mUseDownloadId; }
+
+    /**
      * Creates a builder for {@link DownloadSettings}.
      *
      * @param mainType the filter main type.
@@ -56,6 +68,7 @@
      */
     public static class Builder {
         private final int mMainType;
+        private boolean mUseDownloadId = false;
         private int mDownloadId;
 
         private Builder(int mainType) {
@@ -63,6 +76,24 @@
         }
 
         /**
+         * Sets whether download ID is used or not.
+         *
+         * <p>This configuration is only supported in Tuner 2.0 or higher version. Unsupported
+         * version will cause no-op. Use {@link TunerVersionChecker#getTunerVersion()} to get the
+         * version information.
+         *
+         * <p>Default value is {@code false}.
+         */
+        @NonNull
+        public Builder setUseDownloadId(boolean useDownloadId) {
+            if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0, "setUseDownloadId")) {
+                mUseDownloadId = useDownloadId;
+            }
+            return this;
+        }
+
+        /**
          * Sets download ID.
          */
         @NonNull
@@ -76,7 +107,7 @@
          */
         @NonNull
         public DownloadSettings build() {
-            return new DownloadSettings(mMainType, mDownloadId);
+            return new DownloadSettings(mMainType, mUseDownloadId, mDownloadId);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index f0f576e..5d71a13 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -159,7 +159,8 @@
     public @interface Status {}
 
     /**
-     * The status of a filter that the data in the filter buffer is ready to be read.
+     * The status of a filter that the data in the filter buffer is ready to be read. It can also be
+     * used to know the STC (System Time Clock) ready status if it's PCR filter.
      */
     public static final int STATUS_DATA_READY = DemuxFilterStatus.DATA_READY;
     /**
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index dbd85e9..79d4062 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -40,6 +40,8 @@
     private final int mStreamId;
     private final boolean mIsPtsPresent;
     private final long mPts;
+    private final boolean mIsDtsPresent;
+    private final long mDts;
     private final long mDataLength;
     private final long mOffset;
     private LinearBlock mLinearBlock;
@@ -50,12 +52,14 @@
     private final AudioDescriptor mExtraMetaData;
 
     // This constructor is used by JNI code only
-    private MediaEvent(int streamId, boolean isPtsPresent, long pts, long dataLength, long offset,
-            LinearBlock buffer, boolean isSecureMemory, long dataId, int mpuSequenceNumber,
-            boolean isPrivateData, AudioDescriptor extraMetaData) {
+    private MediaEvent(int streamId, boolean isPtsPresent, long pts, boolean isDtsPresent, long dts,
+            long dataLength, long offset, LinearBlock buffer, boolean isSecureMemory, long dataId,
+            int mpuSequenceNumber, boolean isPrivateData, AudioDescriptor extraMetaData) {
         mStreamId = streamId;
         mIsPtsPresent = isPtsPresent;
         mPts = pts;
+        mIsDtsPresent = isDtsPresent;
+        mDts = dts;
         mDataLength = dataLength;
         mOffset = offset;
         mLinearBlock = buffer;
@@ -90,6 +94,26 @@
     }
 
     /**
+     * Returns whether DTS (Decode Time Stamp) is present.
+     *
+     * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code false}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     *
+     * @return {@code true} if DTS is present in PES header; {@code false} otherwise.
+     */
+    public boolean isDtsPresent() { return mIsDtsPresent; }
+
+    /**
+     * Gets DTS (Decode Time Stamp) for audio or video frame.
+     *
+     * * <p>This query is only supported in Tuner 2.0 or higher version. Unsupported version will
+     * return {@code -1}.
+     * Use {@link TunerVersionChecker#getTunerVersion()} to get the version information.
+     */
+    public long getDts() { return mDts; }
+
+    /**
      * Gets data size in bytes of audio or video frame.
      */
     @BytesLong
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 58a81d9..c8bb178 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -55,7 +55,7 @@
     }
 
     /**
-     * Gets data size in bytes of filtered data.
+     * Gets the record data offset from the beginning of the record buffer.
      */
     @BytesLong
     public long getDataLength() {
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
index ff12492..182bb94 100644
--- a/media/java/android/media/tv/tuner/filter/SectionEvent.java
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -28,10 +28,10 @@
     private final int mTableId;
     private final int mVersion;
     private final int mSectionNum;
-    private final int mDataLength;
+    private final long mDataLength;
 
     // This constructor is used by JNI code only
-    private SectionEvent(int tableId, int version, int sectionNum, int dataLength) {
+    private SectionEvent(int tableId, int version, int sectionNum, long dataLength) {
         mTableId = tableId;
         mVersion = version;
         mSectionNum = sectionNum;
@@ -61,8 +61,13 @@
 
     /**
      * Gets data size in bytes of filtered data.
+     *
+     * @deprecated Use {@link #getDataLengthLong()}
      */
+    @Deprecated
     public int getDataLength() {
-        return mDataLength;
+        return (int) getDataLengthLong();
     }
+
+    public long getDataLengthLong() { return mDataLength; }
 }
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java
index 58e22c9..94fda30 100644
--- a/media/java/android/media/tv/tuner/filter/SectionSettings.java
+++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java
@@ -45,8 +45,19 @@
     public boolean isCrcEnabled() {
         return mCrcEnabled;
     }
+
     /**
-     * Returns whether the filter repeats the data with the same version.
+     * Returns whether the filter repeats the data.
+     *
+     * If {@code false}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections
+     * based on {@link SectionSettingsWithTableInfo} TableId and Version, and stops filtering data.
+     * For {@link SectionSettingsWithSectionBits}, HAL filters out the first section which matches
+     * the {@link SectionSettingsWithSectionBits} configuration, and stops filtering data.
+     *
+     * If {@code true}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections based
+     * on {@link SectionSettingsWithTableInfo} TableId and Version, and repeats. For
+     * {@link SectionSettingsWithSectionBits}, HAL filters out sections which match the
+     * {@link SectionSettingsWithSectionBits} configuration, and repeats.
      */
     public boolean isRepeat() {
         return mIsRepeat;
@@ -83,8 +94,20 @@
             mCrcEnabled = crcEnabled;
             return self();
         }
+
         /**
-         * Sets whether the filter repeats the data with the same version.
+         * Sets whether the filter repeats the data.
+         *
+         * If {@code false}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections
+         * based on {@link SectionSettingsWithTableInfo} TableId and Version, and stops filtering
+         * data. For {@link SectionSettingsWithSectionBits}, HAL filters out the first section which
+         * matches the {@link SectionSettingsWithSectionBits} configuration, and stops filtering
+         * data.
+         *
+         * If {@code true}, for {@link SectionSettingsWithTableInfo}, HAL filters out all sections
+         * based on {@link SectionSettingsWithTableInfo} TableId and Version, and repeats. For
+         * {@link SectionSettingsWithSectionBits}, HAL filters out sections which match the
+         * {@link SectionSettingsWithSectionBits} configuration, and repeats.
          */
         @NonNull
         public T setRepeat(boolean isRepeat) {
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index bf2c000..12ea2224 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -71,7 +71,7 @@
     }
 
     /**
-     * Gets data size in bytes of filtered data.
+     * Gets the record data offset from the beginning of the record buffer.
      */
     @BytesLong
     public long getDataLength() {
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index 31f1a63..0527a1a 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -19,10 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.media.tv.tuner.Lnb;
 import android.media.tv.tuner.TunerVersionChecker;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -53,7 +53,7 @@
             FRONTEND_STATUS_TYPE_MODULATIONS_EXT, FRONTEND_STATUS_TYPE_ROLL_OFF,
             FRONTEND_STATUS_TYPE_IS_MISO_ENABLED, FRONTEND_STATUS_TYPE_IS_LINEAR,
             FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED, FRONTEND_STATUS_TYPE_ISDBT_MODE,
-            FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG})
+            FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG, FRONTEND_STATUS_TYPE_STREAM_ID_LIST})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendStatusType {}
 
@@ -254,6 +254,12 @@
     public static final int FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG =
             android.hardware.tv.tuner.FrontendStatusType.ISDBT_PARTIAL_RECEPTION_FLAG;
 
+    /**
+     * Stream ID list included in a transponder.
+     */
+    public static final int FRONTEND_STATUS_TYPE_STREAM_ID_LIST =
+            android.hardware.tv.tuner.FrontendStatusType.STREAM_ID_LIST;
+
     /** @hide */
     @IntDef(value = {
             AtscFrontendSettings.MODULATION_UNDEFINED,
@@ -493,6 +499,7 @@
     private Boolean mIsShortFrames;
     private Integer mIsdbtMode;
     private Integer mIsdbtPartialReceptionFlag;
+    private int[] mStreamIds;
 
     // Constructed and fields set by JNI code.
     private FrontendStatus() {
@@ -638,6 +645,8 @@
     }
     /**
      * Gets the current Automatic Gain Control value which is normalized from 0 to 255.
+     *
+     * Larger AGC values indicate it is applying more gain.
      */
     public int getAgc() {
         if (mAgc == null) {
@@ -656,6 +665,10 @@
     }
     /**
      * Gets the current Error information by layer.
+     *
+     * The order of the vectors is in ascending order of the required CNR (Contrast-to-noise ratio).
+     * The most robust layer is the first. For example, in ISDB-T, vec[0] is the information of
+     * layer A. vec[1] is the information of layer B.
      */
     @NonNull
     public boolean[] getLayerErrors() {
@@ -729,6 +742,10 @@
      *
      * <p>This query is only supported by Tuner HAL 1.1 or higher. Use
      * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * The order of the vectors is in ascending order of the required CNR (Contrast-to-noise ratio).
+     * The most robust layer is the first. For example, in ISDB-T, vec[0] is the information of
+     * layer A. vec[1] is the information of layer B.
      */
     @NonNull
     public int[] getBers() {
@@ -745,6 +762,10 @@
      *
      * <p>This query is only supported by Tuner HAL 1.1 or higher. Use
      * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * The order of the vectors is in ascending order of the required CNR (Contrast-to-noise ratio).
+     * The most robust layer is the first. For example, in ISDB-T, vec[0] is the information of
+     * layer A. vec[1] is the information of layer B.
      */
     @NonNull
     @FrontendSettings.InnerFec
@@ -842,6 +863,10 @@
      *
      * <p>This query is only supported by Tuner HAL 1.1 or higher. Use
      * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * The order of the vectors is in ascending order of the required CNR (Contrast-to-noise ratio).
+     * The most robust layer is the first. For example, in ISDB-T, vec[0] is the information of
+     * layer A. vec[1] is the information of layer B.
      */
     @NonNull
     @FrontendInterleaveMode
@@ -860,6 +885,10 @@
      *
      * <p>This query is only supported by Tuner HAL 1.1 or higher. Use
      * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * The order of the vectors is in ascending order of the required CNR (Contrast-to-noise ratio).
+     * The most robust layer is the first. For example, in ISDB-T, vec[0] is the information of
+     * layer A. vec[1] is the information of layer B.
      */
     @NonNull
     @IntRange(from = 0, to = 0xff)
@@ -893,6 +922,10 @@
      *
      * <p>This query is only supported by Tuner HAL 1.1 or higher. Use
      * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     *
+     * The order of the vectors is in ascending order of the required CNR (Contrast-to-noise ratio).
+     * The most robust layer is the first. For example, in ISDB-T, vec[0] is the information of
+     * layer A. vec[1] is the information of layer B.
      */
     @NonNull
     @FrontendModulation
@@ -1001,6 +1034,24 @@
     }
 
     /**
+     * Gets stream id list included in a transponder.
+     *
+     * <p>This query is only supported by Tuner HAL 2.0 or higher. Unsupported version or if HAL
+     * doesn't return stream id list status will throw IllegalStateException. Use
+     * {@link TunerVersionChecker#getTunerVersion()} to check the version.
+     */
+    @SuppressLint("ArrayReturn")
+    @NonNull
+    public int[] getStreamIdList() {
+        TunerVersionChecker.checkHigherOrEqualVersionTo(
+                TunerVersionChecker.TUNER_VERSION_2_0, "stream id list status");
+        if (mStreamIds == null) {
+            throw new IllegalStateException("stream id list status is empty");
+        }
+        return mStreamIds;
+    }
+
+    /**
      * Information of each tuning Physical Layer Pipes.
      */
     public static class Atsc3PlpTuningInfo {
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 403bfa7..89512a0 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -546,14 +546,14 @@
         private final int mModulation;
         private final int mTimeInterleaveMode;
         private final int mCodeRate;
-        private final int mNumOfSegment;
+        private final int mNumOfSegments;
 
         private IsdbtLayerSettings(
-                int modulation, int timeInterleaveMode, int codeRate, int numOfSegment) {
+                int modulation, int timeInterleaveMode, int codeRate, int numOfSegments) {
             mModulation = modulation;
             mTimeInterleaveMode = timeInterleaveMode;
             mCodeRate = codeRate;
-            mNumOfSegment = numOfSegment;
+            mNumOfSegments = numOfSegments;
         }
 
         /**
@@ -578,11 +578,11 @@
             return mCodeRate;
         }
         /**
-         * Gets Number of Segment.
+         * Gets Number of Segments.
          */
         @IntRange(from = 0, to = 0xff)
-        public int getNumberOfSegment() {
-            return mNumOfSegment;
+        public int getNumberOfSegments() {
+            return mNumOfSegments;
         }
 
         /**
@@ -600,7 +600,7 @@
             private int mModulation = MODULATION_UNDEFINED;
             private int mTimeInterleaveMode = TIME_INTERLEAVE_MODE_UNDEFINED;
             private int mCodeRate = DvbtFrontendSettings.CODERATE_UNDEFINED;
-            private int mNumOfSegment = 0;
+            private int mNumOfSegments = 0;
 
             private Builder() {}
 
@@ -633,14 +633,14 @@
                 return this;
             }
             /**
-             * Sets number of segment.
+             * Sets number of segments.
              *
              * <p>Default value is 0.
              */
             @NonNull
             @IntRange(from = 0, to = 0xff)
-            public Builder setNumberOfSegment(int numOfSegment) {
-                mNumOfSegment = numOfSegment;
+            public Builder setNumberOfSegments(int numOfSegments) {
+                mNumOfSegments = numOfSegments;
                 return this;
             }
 
@@ -650,7 +650,7 @@
             @NonNull
             public IsdbtLayerSettings build() {
                 return new IsdbtLayerSettings(
-                        mModulation, mTimeInterleaveMode, mCodeRate, mNumOfSegment);
+                        mModulation, mTimeInterleaveMode, mCodeRate, mNumOfSegments);
             }
         }
     }
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index f61bd52..cb35edb 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -28,7 +28,12 @@
 @SystemApi
 public interface ScanCallback {
 
-    /** Scan locked the signal. */
+    /**
+     * Scan locked the signal.
+     *
+     * It can also be notified after signal is locked if the signal attributes transmission
+     * parameter of the signal is changed (e.g., Modulation).
+     */
     void onLocked();
 
     /** Scan stopped. */
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 244fd0e..fe611c7 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -320,6 +320,48 @@
     }
 
     /**
+     * Grants the lock to the caller for public {@link Tuner} APIs
+     *
+     * <p>{@link Tuner} functions that call both [@link TunerResourceManager} APIs and
+     * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()}
+     * must call this API before acquiring lock used in onReclaimResources().
+     *
+     * <p>This API will block until it releases the lock or fails
+     *
+     * @param clientId The ID of the caller.
+     *
+     * @return true if the lock is granted. If false is returned, calling this API again is not
+     * guaranteed to work and may be unrecoverrable. (This should not happen.)
+     */
+    public boolean acquireLock(int clientId) {
+        try {
+            return mService.acquireLock(clientId, Thread.currentThread().getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Releases the lock to the caller for public {@link Tuner} APIs
+     *
+     * <p>This API must be called in pair with {@link #acquireLock(int, int)}
+     *
+     * <p>This API will block until it releases the lock or fails
+     *
+     * @param clientId The ID of the caller.
+     *
+     * @return true if the lock is granted. If false is returned, calling this API again is not
+     * guaranteed to work and may be unrecoverrable. (This should not happen.)
+     */
+    public boolean releaseLock(int clientId) {
+        try {
+            return mService.releaseLock(clientId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Requests a frontend resource.
      *
      * <p>There are three possible scenarios:
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 7bc5058..5f35820 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -412,4 +412,34 @@
      * @param resourceType The resource type to restore the map for.
      */
     void restoreResourceMap(in int resourceType);
+
+    /**
+     * Grants the lock to the caller for public {@link Tuner} APIs
+     *
+     * <p>{@link Tuner} functions that call both [@link TunerResourceManager} APIs and
+     * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()}
+     * must call this API before acquiring lock used in onReclaimResources().
+     *
+     * <p>This API will block until it releases the lock or fails
+     *
+     * @param clientId The ID of the caller.
+     *
+     * @return true if the lock is granted. If false is returned, calling this API again is not
+     * guaranteed to work and may be unrecoverrable. (This should not happen.)
+     */
+    boolean acquireLock(in int clientId, in long clientThreadId);
+
+    /**
+     * Releases the lock to the caller for public {@link Tuner} APIs
+     *
+     * <p>This API must be called in pair with {@link #acquireLock(int, int)}
+     *
+     * <p>This API will block until it releases the lock or fails
+     *
+     * @param clientId The ID of the caller.
+     *
+     * @return true if the lock is granted. If false is returned, calling this API again is not
+     * guaranteed to work and may be unrecoverrable. (This should not happen.)
+     */
+    boolean releaseLock(in int clientId);
 }
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4c1ed45..e91e238 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -588,13 +588,13 @@
                                                const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/SectionEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIII)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIJ)V");
 
     const DemuxFilterSectionEvent &sectionEvent = event.get<DemuxFilterEvent::Tag::section>();
     jint tableId = sectionEvent.tableId;
     jint version = sectionEvent.version;
     jint sectionNum = sectionEvent.sectionNum;
-    jint dataLength = sectionEvent.dataLength;
+    jlong dataLength = sectionEvent.dataLength;
 
     jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
@@ -604,10 +604,9 @@
                                              const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/MediaEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz,
-            "<init>",
-            "(IZJJJLandroid/media/MediaCodec$LinearBlock;"
-            "ZJIZLandroid/media/tv/tuner/filter/AudioDescriptor;)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>",
+                                           "(IZJZJJJLandroid/media/MediaCodec$LinearBlock;"
+                                           "ZJIZLandroid/media/tv/tuner/filter/AudioDescriptor;)V");
     jfieldID eventContext = env->GetFieldID(eventClazz, "mNativeContext", "J");
 
     const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
@@ -633,15 +632,17 @@
     jint streamId = mediaEvent.streamId;
     jboolean isPtsPresent = mediaEvent.isPtsPresent;
     jlong pts = mediaEvent.pts;
+    jboolean isDtsPresent = mediaEvent.isDtsPresent;
+    jlong dts = mediaEvent.dts;
     jlong offset = mediaEvent.offset;
     jboolean isSecureMemory = mediaEvent.isSecureMemory;
     jlong avDataId = mediaEvent.avDataId;
     jint mpuSequenceNumber = mediaEvent.mpuSequenceNumber;
     jboolean isPesPrivateData = mediaEvent.isPesPrivateData;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, dataLength,
-                                 offset, nullptr, isSecureMemory, avDataId, mpuSequenceNumber,
-                                 isPesPrivateData, audioDescriptor);
+    jobject obj = env->NewObject(eventClazz, eventInit, streamId, isPtsPresent, pts, isDtsPresent,
+                                 dts, dataLength, offset, nullptr, isSecureMemory, avDataId,
+                                 mpuSequenceNumber, isPesPrivateData, audioDescriptor);
 
     uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
     if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
@@ -733,16 +734,17 @@
                                                 const DemuxFilterEvent &event) {
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass eventClazz = env->FindClass("android/media/tv/tuner/filter/DownloadEvent");
-    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIII)V");
+    jmethodID eventInit = env->GetMethodID(eventClazz, "<init>", "(IIIIII)V");
 
     const DemuxFilterDownloadEvent &downloadEvent = event.get<DemuxFilterEvent::Tag::download>();
     jint itemId = downloadEvent.itemId;
+    jint downloadId = downloadEvent.downloadId;
     jint mpuSequenceNumber = downloadEvent.mpuSequenceNumber;
     jint itemFragmentIndex = downloadEvent.itemFragmentIndex;
     jint lastItemFragmentIndex = downloadEvent.lastItemFragmentIndex;
     jint dataLength = downloadEvent.dataLength;
 
-    jobject obj = env->NewObject(eventClazz, eventInit, itemId, mpuSequenceNumber,
+    jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
                                  itemFragmentIndex, lastItemFragmentIndex, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
 }
@@ -951,20 +953,45 @@
 }
 
 /////////////// FrontendClientCallbackImpl ///////////////////////
-FrontendClientCallbackImpl::FrontendClientCallbackImpl(jweak tunerObj) : mObject(tunerObj) {}
+FrontendClientCallbackImpl::FrontendClientCallbackImpl(JTuner* jtuner, jweak listener) {
+    ALOGV("FrontendClientCallbackImpl() with listener:%p", listener);
+    addCallbackListener(jtuner, listener);
+}
+
+void FrontendClientCallbackImpl::addCallbackListener(JTuner* jtuner, jweak listener) {
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jweak listenerRef = env->NewWeakGlobalRef(listener);
+    ALOGV("addCallbackListener() with listener:%p and ref:%p @%p", listener, listenerRef, this);
+    std::scoped_lock<std::mutex> lock(mMutex);
+    mListenersMap[jtuner] = listenerRef;
+}
+
+void FrontendClientCallbackImpl::removeCallbackListener(JTuner* listener) {
+    ALOGV("removeCallbackListener for listener:%p", listener);
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    std::scoped_lock<std::mutex> lock(mMutex);
+    if (mListenersMap.find(listener) != mListenersMap.end() && mListenersMap[listener]) {
+        env->DeleteWeakGlobalRef(mListenersMap[listener]);
+        mListenersMap.erase(listener);
+    }
+}
 
 void FrontendClientCallbackImpl::onEvent(FrontendEventType frontendEventType) {
     ALOGV("FrontendClientCallbackImpl::onEvent, type=%d", frontendEventType);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
-    jobject frontend(env->NewLocalRef(mObject));
-    if (!env->IsSameObject(frontend, nullptr)) {
-        env->CallVoidMethod(
-                frontend,
-                gFields.onFrontendEventID,
-                (jint)frontendEventType);
-    } else {
-        ALOGE("FrontendClientCallbackImpl::onEvent:"
-                "Frontend object has been freed. Ignoring callback.");
+    std::scoped_lock<std::mutex> lock(mMutex);
+    for (const auto& mapEntry : mListenersMap) {
+        ALOGV("JTuner:%p, jweak:%p", mapEntry.first, mapEntry.second);
+        jobject frontend(env->NewLocalRef(mapEntry.second));
+        if (!env->IsSameObject(frontend, nullptr)) {
+            env->CallVoidMethod(
+                    frontend,
+                    gFields.onFrontendEventID,
+                    (jint)frontendEventType);
+        } else {
+            ALOGW("FrontendClientCallbackImpl::onEvent:"
+                    "Frontend object has been freed. Ignoring callback.");
+        }
     }
 }
 
@@ -973,12 +1000,25 @@
     ALOGV("FrontendClientCallbackImpl::onScanMessage, type=%d", type);
     JNIEnv *env = AndroidRuntime::getJNIEnv();
     jclass clazz = env->FindClass("android/media/tv/tuner/Tuner");
-    jobject frontend(env->NewLocalRef(mObject));
-    if (env->IsSameObject(frontend, nullptr)) {
-        ALOGE("FrontendClientCallbackImpl::onScanMessage:"
-                "Frontend object has been freed. Ignoring callback.");
-        return;
+
+    std::scoped_lock<std::mutex> lock(mMutex);
+    for (const auto& mapEntry : mListenersMap) {
+        jobject frontend(env->NewLocalRef(mapEntry.second));
+        if (env->IsSameObject(frontend, nullptr)) {
+            ALOGE("FrontendClientCallbackImpl::onScanMessage:"
+                    "Tuner object has been freed. Ignoring callback.");
+            continue;
+        }
+        executeOnScanMessage(env, clazz, frontend, type, message);
     }
+}
+
+void FrontendClientCallbackImpl::executeOnScanMessage(
+         JNIEnv *env, const jclass& clazz, const jobject& frontend,
+         FrontendScanMessageType type,
+         const FrontendScanMessage& message) {
+    ALOGV("FrontendClientCallbackImpl::executeOnScanMessage, type=%d", type);
+
     switch(type) {
         case FrontendScanMessageType::LOCKED: {
             if (message.get<FrontendScanMessage::Tag::isLocked>()) {
@@ -1157,11 +1197,14 @@
 }
 
 FrontendClientCallbackImpl::~FrontendClientCallbackImpl() {
-    JNIEnv *env = AndroidRuntime::getJNIEnv();
-    if (mObject != nullptr) {
-        env->DeleteWeakGlobalRef(mObject);
-        mObject = nullptr;
+    JNIEnv *env = android::AndroidRuntime::getJNIEnv();
+    ALOGV("~FrontendClientCallbackImpl()");
+    std::scoped_lock<std::mutex> lock(mMutex);
+    for (const auto& mapEntry : mListenersMap) {
+        ALOGV("deleteRef :%p at @ %p", mapEntry.second, this);
+        env->DeleteWeakGlobalRef(mapEntry.second);
     }
+    mListenersMap.clear();
 }
 
 /////////////// Tuner ///////////////////////
@@ -1180,6 +1223,10 @@
     mSharedFeId = (int)Constant::INVALID_FRONTEND_ID;
 }
 
+jweak JTuner::getObject() {
+    return mObject;
+}
+
 JTuner::~JTuner() {
     if (mFeClient != nullptr) {
         mFeClient->close();
@@ -1192,6 +1239,7 @@
     env->DeleteWeakGlobalRef(mObject);
     env->DeleteGlobalRef(mClass);
     mFeClient = nullptr;
+    mFeClientCb = nullptr;
     mDemuxClient = nullptr;
     mClass = nullptr;
     mObject = nullptr;
@@ -1247,9 +1295,8 @@
         return nullptr;
     }
 
-    sp<FrontendClientCallbackImpl> feClientCb =
-            new FrontendClientCallbackImpl(env->NewWeakGlobalRef(mObject));
-    mFeClient->setCallback(feClientCb);
+    mFeClientCb = new FrontendClientCallbackImpl(this, mObject);
+    mFeClient->setCallback(mFeClientCb);
     // TODO: add more fields to frontend
     return env->NewObject(
             env->FindClass("android/media/tv/tuner/Tuner$Frontend"),
@@ -1269,6 +1316,18 @@
     return (int)Result::SUCCESS;
 }
 
+void JTuner::registerFeCbListener(JTuner* jtuner) {
+    if (mFeClientCb != nullptr && jtuner != nullptr) {
+        mFeClientCb->addCallbackListener(jtuner, jtuner->getObject());
+    }
+}
+
+void JTuner::unregisterFeCbListener(JTuner* jtuner) {
+    if (mFeClientCb != nullptr && jtuner != nullptr) {
+        mFeClientCb->removeCallbackListener(jtuner);
+    }
+}
+
 jobject JTuner::getAnalogFrontendCaps(JNIEnv *env, FrontendCapabilities &caps) {
     jclass clazz = env->FindClass("android/media/tv/tuner/frontend/AnalogFrontendCapabilities");
     jmethodID capsInit = env->GetMethodID(clazz, "<init>", "(II)V");
@@ -2450,7 +2509,14 @@
                 env->SetObjectField(statusObj, field, newIntegerObj);
                 break;
             }
-            default: {
+            case FrontendStatus::Tag::streamIdList: {
+                jfieldID field = env->GetFieldID(clazz, "mStreamIds", "[I");
+                std::vector<int32_t> ids = s.get<FrontendStatus::Tag::streamIdList>();
+
+                jintArray valObj = env->NewIntArray(v.size());
+                env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
+
+                env->SetObjectField(statusObj, field, valObj);
                 break;
             }
         }
@@ -2961,7 +3027,7 @@
         frontendIsdbtSettings.layerSettings[i].coderate = static_cast<FrontendIsdbtCoderate>(
                 env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
         frontendIsdbtSettings.layerSettings[i].numOfSegment =
-                env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegment", "I"));
+                env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
     }
 
     frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
@@ -3188,6 +3254,20 @@
     return tuner->shareFrontend(id);
 }
 
+static void android_media_tv_Tuner_register_fe_cb_listener(
+        JNIEnv *env, jobject thiz, jlong shareeJTuner) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    JTuner *jtuner = (JTuner *)shareeJTuner;
+    tuner->registerFeCbListener(jtuner);
+}
+
+static void android_media_tv_Tuner_unregister_fe_cb_listener(
+        JNIEnv *env, jobject thiz, jlong shareeJTuner) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    JTuner *jtuner = (JTuner *)shareeJTuner;
+    tuner->unregisterFeCbListener(jtuner);
+}
+
 static int android_media_tv_Tuner_tune(JNIEnv *env, jobject thiz, jint type, jobject settings) {
     sp<JTuner> tuner = getTuner(env, thiz);
     FrontendSettings setting = getFrontendSettings(env, type, settings);
@@ -3469,10 +3549,13 @@
 
 static DemuxFilterDownloadSettings getFilterDownloadSettings(JNIEnv *env, const jobject& settings) {
     jclass clazz = env->FindClass("android/media/tv/tuner/filter/DownloadSettings");
+    bool useDownloadId =
+            env->GetBooleanField(settings, env->GetFieldID(clazz, "mUseDownloadId", "Z"));
     int32_t downloadId = env->GetIntField(settings, env->GetFieldID(clazz, "mDownloadId", "I"));
 
-    DemuxFilterDownloadSettings filterDownloadSettings {
-        .downloadId = downloadId,
+    DemuxFilterDownloadSettings filterDownloadSettings{
+            .useDownloadId = useDownloadId,
+            .downloadId = downloadId,
     };
     return filterDownloadSettings;
 }
@@ -4253,6 +4336,17 @@
     return (jlong)dvrClient->readFromFile(size);
 }
 
+static jlong android_media_tv_Tuner_seek_dvr(JNIEnv *env, jobject dvr, jlong pos) {
+    sp<DvrClient> dvrClient = getDvrClient(env, dvr);
+    if (dvrClient == nullptr) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                          "Failed to seek dvr: dvr client not found");
+        return -1;
+    }
+
+    return (jlong)dvrClient->seekFile(pos);
+}
+
 static jlong android_media_tv_Tuner_read_dvr_from_array(
         JNIEnv* env, jobject dvr, jbyteArray buffer, jlong offset, jlong size) {
     sp<DvrClient> dvrClient = getDvrClient(env, dvr);
@@ -4361,6 +4455,10 @@
             (void *)android_media_tv_Tuner_open_frontend_by_handle },
     { "nativeShareFrontend", "(I)I",
             (void *)android_media_tv_Tuner_share_frontend },
+    { "nativeRegisterFeCbListener", "(J)V",
+            (void*)android_media_tv_Tuner_register_fe_cb_listener },
+    { "nativeUnregisterFeCbListener", "(J)V",
+            (void*)android_media_tv_Tuner_unregister_fe_cb_listener },
     { "nativeTune", "(ILandroid/media/tv/tuner/frontend/FrontendSettings;)I",
             (void *)android_media_tv_Tuner_tune },
     { "nativeStopTune", "()I", (void *)android_media_tv_Tuner_stop_tune },
@@ -4403,38 +4501,37 @@
     { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_tuner },
     { "nativeCloseFrontend", "(I)I", (void *)android_media_tv_Tuner_close_frontend },
     { "nativeCloseDemux", "(I)I", (void *)android_media_tv_Tuner_close_demux },
-    {"nativeOpenSharedFilter",
+    { "nativeOpenSharedFilter",
             "(Ljava/lang/String;)Landroid/media/tv/tuner/filter/SharedFilter;",
             (void *)android_media_tv_Tuner_open_shared_filter},
 };
 
 static const JNINativeMethod gFilterMethods[] = {
     { "nativeConfigureFilter", "(IILandroid/media/tv/tuner/filter/FilterConfiguration;)I",
-            (void *)android_media_tv_Tuner_configure_filter },
-    { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id },
-    { "nativeGetId64Bit", "()J",
-            (void *)android_media_tv_Tuner_get_filter_64bit_id },
+            (void *)android_media_tv_Tuner_configure_filter},
+    { "nativeGetId", "()I", (void *)android_media_tv_Tuner_get_filter_id},
+    { "nativeGetId64Bit", "()J", (void *)android_media_tv_Tuner_get_filter_64bit_id},
     { "nativeConfigureMonitorEvent", "(I)I",
-            (void *)android_media_tv_Tuner_configure_monitor_event },
+            (void *)android_media_tv_Tuner_configure_monitor_event},
     { "nativeSetDataSource", "(Landroid/media/tv/tuner/filter/Filter;)I",
-            (void *)android_media_tv_Tuner_set_filter_data_source },
-    { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter },
-    { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter },
-    { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter },
-    { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq },
-    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
-    {"nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
+            (void *)android_media_tv_Tuner_set_filter_data_source},
+    { "nativeStartFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+    { "nativeStopFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+    { "nativeFlushFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+    { "nativeRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter},
+    { "nativeAcquireSharedFilterToken", "()Ljava/lang/String;",
             (void *)android_media_tv_Tuner_acquire_shared_filter_token},
-    {"nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
+    { "nativeFreeSharedFilterToken", "(Ljava/lang/String;)V",
             (void *)android_media_tv_Tuner_free_shared_filter_token},
 };
 
 static const JNINativeMethod gSharedFilterMethods[] = {
-    {"nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
-    {"nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
-    {"nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
-    {"nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
-    {"nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
+    { "nativeStartSharedFilter", "()I", (void *)android_media_tv_Tuner_start_filter},
+    { "nativeStopSharedFilter", "()I", (void *)android_media_tv_Tuner_stop_filter},
+    { "nativeFlushSharedFilter", "()I", (void *)android_media_tv_Tuner_flush_filter},
+    { "nativeSharedRead", "([BJJ)I", (void *)android_media_tv_Tuner_read_filter_fmq},
+    { "nativeSharedClose", "()I", (void *)android_media_tv_Tuner_close_filter},
 };
 
 static const JNINativeMethod gTimeFilterMethods[] = {
@@ -4474,18 +4571,19 @@
 
 static const JNINativeMethod gDvrPlaybackMethods[] = {
     { "nativeAttachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
-            (void *)android_media_tv_Tuner_attach_filter },
+            (void *)android_media_tv_Tuner_attach_filter},
     { "nativeDetachFilter", "(Landroid/media/tv/tuner/filter/Filter;)I",
-            (void *)android_media_tv_Tuner_detach_filter },
+            (void *)android_media_tv_Tuner_detach_filter},
     { "nativeConfigureDvr", "(Landroid/media/tv/tuner/dvr/DvrSettings;)I",
-            (void *)android_media_tv_Tuner_configure_dvr },
-    { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr },
-    { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
-    { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
-    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr },
-    { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd },
-    { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr },
-    { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array },
+            (void *)android_media_tv_Tuner_configure_dvr},
+    { "nativeStartDvr", "()I", (void *)android_media_tv_Tuner_start_dvr},
+    { "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr},
+    { "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr},
+    { "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr},
+    { "nativeSetFileDescriptor", "(I)V", (void *)android_media_tv_Tuner_dvr_set_fd},
+    { "nativeRead", "(J)J", (void *)android_media_tv_Tuner_read_dvr},
+    { "nativeRead", "([BJJ)J", (void *)android_media_tv_Tuner_read_dvr_from_array},
+    { "nativeSeek", "(J)J", (void *)android_media_tv_Tuner_seek_dvr},
 };
 
 static const JNINativeMethod gLnbMethods[] = {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 31d24ee..06e2492 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -149,14 +149,21 @@
     void getRestartEvent(jobjectArray& arr, const int size, const DemuxFilterEvent& event);
 };
 
+struct JTuner;
 struct FrontendClientCallbackImpl : public FrontendClientCallback {
-    FrontendClientCallbackImpl(jweak tunerObj);
+    FrontendClientCallbackImpl(JTuner*, jweak);
     ~FrontendClientCallbackImpl();
     virtual void onEvent(FrontendEventType frontendEventType);
     virtual void onScanMessage(
             FrontendScanMessageType type, const FrontendScanMessage& message);
 
-    jweak mObject;
+    void executeOnScanMessage(JNIEnv *env, const jclass& clazz, const jobject& frontend,
+                              FrontendScanMessageType type,
+                              const FrontendScanMessage& message);
+    void addCallbackListener(JTuner*, jweak obj);
+    void removeCallbackListener(JTuner* jtuner);
+    std::unordered_map<JTuner*, jweak> mListenersMap;
+    std::mutex mMutex;
 };
 
 struct JTuner : public RefBase {
@@ -171,6 +178,8 @@
     jobject getFrontendIds();
     jobject openFrontendByHandle(int feHandle);
     int shareFrontend(int feId);
+    void registerFeCbListener(JTuner* jtuner);
+    void unregisterFeCbListener(JTuner* jtuner);
     jint closeFrontendById(int id);
     jobject getFrontendInfo(int id);
     int tune(const FrontendSettings& settings);
@@ -192,6 +201,8 @@
     jint closeFrontend();
     jint closeDemux();
 
+    jweak getObject();
+
 protected:
     virtual ~JTuner();
 
@@ -200,6 +211,7 @@
     jweak mObject;
     static sp<TunerClient> mTunerClient;
     sp<FrontendClient> mFeClient;
+    sp<FrontendClientCallbackImpl> mFeClientCb;
     int mFeId;
     int mSharedFeId;
     sp<LnbClient> mLnbClient;
diff --git a/media/jni/soundpool/SoundPool.cpp b/media/jni/soundpool/SoundPool.cpp
index 253b4e3..00c4a97 100644
--- a/media/jni/soundpool/SoundPool.cpp
+++ b/media/jni/soundpool/SoundPool.cpp
@@ -185,7 +185,7 @@
     auto apiLock = kUseApiLock ? std::make_unique<std::lock_guard<std::mutex>>(mApiLock) : nullptr;
     soundpool::Stream* stream = mStreamManager.findStream(streamID);
     if (stream != nullptr && stream->requestStop(streamID)) {
-        mStreamManager.moveToRestartQueue(stream);
+        mStreamManager.moveToRestartQueue(stream, streamID);
     }
 }
 
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index bbbef38..773cdc9 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -228,10 +228,9 @@
    return mStreamManager->getPairStream(this);
 }
 
-Stream* Stream::playPairStream() {
+Stream* Stream::playPairStream(std::vector<std::any>& garbage) {
     Stream* pairStream = getPairStream();
     LOG_ALWAYS_FATAL_IF(pairStream == nullptr, "No pair stream!");
-    sp<AudioTrack> releaseTracks[2];
     {
         ALOGV("%s: track streamID: %d", __func__, (int)getStreamID());
         // TODO: Do we really want to force a simultaneous synchronization between
@@ -260,7 +259,7 @@
         const int pairState = pairStream->mState;
         pairStream->play_l(pairStream->mSound, pairStream->mStreamID,
                 pairStream->mLeftVolume, pairStream->mRightVolume, pairStream->mPriority,
-                pairStream->mLoop, pairStream->mRate, releaseTracks);
+                pairStream->mLoop, pairStream->mRate, garbage);
         if (pairStream->mState == IDLE) {
             return nullptr; // AudioTrack error
         }
@@ -269,17 +268,16 @@
             pairStream->mAudioTrack->pause();
         }
     }
-    // release tracks outside of Stream lock
     return pairStream;
 }
 
 void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID,
         float leftVolume, float rightVolume, int32_t priority, int32_t loop, float rate,
-        sp<AudioTrack> releaseTracks[2])
+        std::vector<std::any>& garbage)
 {
-    // These tracks are released without the lock.
-    sp<AudioTrack> &oldTrack = releaseTracks[0];
-    sp<AudioTrack> &newTrack = releaseTracks[1];
+    // oldTrack and newTrack are placeholders to be released by garbage without the lock.
+    sp<AudioTrack> oldTrack;
+    sp<AudioTrack> newTrack;
     status_t status = NO_ERROR;
 
     {
@@ -383,8 +381,12 @@
         mState = IDLE;
         mSoundID = 0;
         mSound.reset();
-        mAudioTrack.clear();  // actual release from releaseTracks[]
+        mAudioTrack.clear();  // actual release from garbage
     }
+
+    // move tracks to garbage to be released later outside of lock.
+    if (newTrack) garbage.emplace_back(std::move(newTrack));
+    if (oldTrack) garbage.emplace_back(std::move(oldTrack));
 }
 
 /* static */
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
index d4e5c9f..aa0eef5 100644
--- a/media/jni/soundpool/Stream.h
+++ b/media/jni/soundpool/Stream.h
@@ -18,6 +18,7 @@
 
 #include "Sound.h"
 
+#include <any>
 #include <android-base/thread_annotations.h>
 #include <audio_utils/clock.h>
 #include <media/AudioTrack.h>
@@ -90,8 +91,9 @@
     void mute(bool muting);
     void dump() const NO_THREAD_SAFETY_ANALYSIS; // disable for ALOGV (see func for details).
 
-    // returns the pair stream if successful, nullptr otherwise
-    Stream* playPairStream();
+    // returns the pair stream if successful, nullptr otherwise.
+    // garbage is used to release tracks and data outside of any lock.
+    Stream* playPairStream(std::vector<std::any>& garbage);
 
     // These parameters are explicitly checked in the SoundPool class
     // so never deviate from the Java API specified values.
@@ -123,9 +125,10 @@
     Stream* getPairStream() const;
 
 private:
+    // garbage is used to release tracks and data outside of any lock.
     void play_l(const std::shared_ptr<Sound>& sound, int streamID,
             float leftVolume, float rightVolume, int priority, int loop, float rate,
-            sp<AudioTrack> releaseTracks[2]) REQUIRES(mLock);
+            std::vector<std::any>& garbage) REQUIRES(mLock);
     void stop_l() REQUIRES(mLock);
     void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
 
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 309d71c..7f987e3 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -157,6 +157,7 @@
             __func__, sound.get(), soundID, leftVolume, rightVolume, priority, loop, rate);
     bool launchThread = false;
     int32_t streamID = 0;
+    std::vector<std::any> garbage;
 
     { // for lock
         std::unique_lock lock(mStreamManagerLock);
@@ -243,7 +244,7 @@
             removeFromQueues_l(newStream);
             mProcessingStreams.emplace(newStream);
             lock.unlock();
-            if (Stream* nextStream = newStream->playPairStream()) {
+            if (Stream* nextStream = newStream->playPairStream(garbage)) {
                 lock.lock();
                 ALOGV("%s: starting streamID:%d", __func__, nextStream->getStreamID());
                 addToActiveQueue_l(nextStream);
@@ -266,6 +267,7 @@
         ALOGV_IF(id != 0, "%s: launched thread %d", __func__, id);
     }
     ALOGV("%s: returning %d", __func__, streamID);
+    // garbage is cleared here outside mStreamManagerLock.
     return streamID;
 }
 
@@ -359,6 +361,7 @@
 {
     ALOGV("%s(%d) entering", __func__, id);
     int64_t waitTimeNs = 0;  // on thread start, mRestartStreams can be non-empty.
+    std::vector<std::any> garbage; // used for garbage collection
     std::unique_lock lock(mStreamManagerLock);
     while (!mQuit) {
         if (waitTimeNs > 0) {
@@ -388,7 +391,7 @@
             if (!mLockStreamManagerStop) lock.unlock();
             stream->stop();
             ALOGV("%s(%d) stopping streamID:%d", __func__, id, stream->getStreamID());
-            if (Stream* nextStream = stream->playPairStream()) {
+            if (Stream* nextStream = stream->playPairStream(garbage)) {
                 ALOGV("%s(%d) starting streamID:%d", __func__, id, nextStream->getStreamID());
                 if (!mLockStreamManagerStop) lock.lock();
                 if (nextStream->getStopTimeNs() > 0) {
@@ -405,6 +408,12 @@
             }
             mProcessingStreams.erase(stream);
             sanityCheckQueue_l();
+            if (!garbage.empty()) {
+                lock.unlock();
+                // garbage audio tracks (etc) are cleared here outside mStreamManagerLock.
+                garbage.clear();
+                lock.lock();
+            }
         }
     }
     ALOGV("%s(%d) exiting", __func__, id);
diff --git a/media/jni/tuner/DvrClient.cpp b/media/jni/tuner/DvrClient.cpp
index 3027838..05683b6 100644
--- a/media/jni/tuner/DvrClient.cpp
+++ b/media/jni/tuner/DvrClient.cpp
@@ -22,6 +22,8 @@
 #include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
 #include <android-base/logging.h>
 #include <inttypes.h>
+#include <sys/types.h>
+#include <unistd.h>
 #include <utils/Log.h>
 
 #include "ClientHelper.h"
@@ -200,6 +202,14 @@
     return size;
 }
 
+int64_t DvrClient::seekFile(int64_t pos) {
+    if (mFd < 0) {
+        ALOGE("Failed to seekFile. File is not configured");
+        return -1;
+    }
+    return lseek64(mFd, pos, SEEK_SET);
+}
+
 Result DvrClient::configure(DvrSettings settings) {
     if (mTunerDvr != nullptr) {
         Status s = mTunerDvr->configure(settings);
diff --git a/media/jni/tuner/DvrClient.h b/media/jni/tuner/DvrClient.h
index 9080c72..61c0325 100644
--- a/media/jni/tuner/DvrClient.h
+++ b/media/jni/tuner/DvrClient.h
@@ -82,6 +82,11 @@
     int64_t writeToFile(int64_t size);
 
     /**
+     * Seeks the Dvr file descriptor from the beginning of the file.
+     */
+    int64_t seekFile(int64_t pos);
+
+    /**
      * Write data to the given buffer with given size. Return the actual write size.
      */
     int64_t writeToBuffer(int8_t* buffer, int64_t size);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 6dc05ad..a2eae2c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -303,6 +303,11 @@
         public void onCameraClosed(String cameraId) {
             Log.v(TAG, String.format("Camera %s is closed", cameraId));
         }
+        @Override
+        public void onTorchStrengthLevelChanged(String cameraId, int torchStrength) {
+            Log.v(TAG, String.format("Camera " + cameraId + " torch strength level changed to "
+                    + torchStrength ));
+        }
     }
 
     /**
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 4de2c23..c06c81e 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -330,6 +330,6 @@
     iq->finishEvent(e, handled != 0);
 }
 
-AInputQueue* AInputQueue_fromJava(jobject inputQueue) {
-    return android::android_view_InputQueue_getNativePtr(inputQueue);
+AInputQueue* AInputQueue_fromJava(JNIEnv* env, jobject inputQueue) {
+    return android::android_view_InputQueue_getNativePtr(env, inputQueue);
 }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index f00eef2..3c1aa44 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -263,6 +263,7 @@
     ASurfaceTransaction_setEnableBackPressure; # introduced=31
     ASurfaceTransaction_setFrameRate; # introduced=30
     ASurfaceTransaction_setFrameRateWithChangeStrategy; # introduced=31
+    ASurfaceTransaction_setFrameTimeline; # introduced=Tiramisu
     ASurfaceTransaction_setGeometry; # introduced=29
     ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
     ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index ceba4d6..5b19102 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -364,7 +364,7 @@
     sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
     Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
 
-    sp<GraphicBuffer> graphic_buffer(reinterpret_cast<GraphicBuffer*>(buffer));
+    sp<GraphicBuffer> graphic_buffer(GraphicBuffer::fromAHardwareBuffer(buffer));
 
     std::optional<sp<Fence>> fence = std::nullopt;
     if (acquire_fence_fd != -1) {
@@ -659,3 +659,11 @@
 
     transaction->addTransactionCommittedCallback(callback, context);
 }
+
+void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* aSurfaceTransaction,
+                                          int64_t vsyncId) {
+    CHECK_NOT_NULL(aSurfaceTransaction);
+    // TODO(b/210043506): Get start time from platform.
+    ASurfaceTransaction_to_Transaction(aSurfaceTransaction)
+            ->setFrameTimelineInfo({.vsyncId = vsyncId, .startTimeNanos = 0});
+}
diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml
index 3e65df2..632dfb3 100644
--- a/packages/CarrierDefaultApp/AndroidManifest.xml
+++ b/packages/CarrierDefaultApp/AndroidManifest.xml
@@ -27,6 +27,7 @@
     <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
     <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" />
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application
         android:label="@string/app_name"
diff --git a/packages/CompanionDeviceManager/AndroidManifest.xml b/packages/CompanionDeviceManager/AndroidManifest.xml
index c5926a5..06f2d9d 100644
--- a/packages/CompanionDeviceManager/AndroidManifest.xml
+++ b/packages/CompanionDeviceManager/AndroidManifest.xml
@@ -19,10 +19,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.companiondevicemanager">
 
-    <permission
-        android:name="com.android.companiondevicemanager.permission.BIND"
-        android:protectionLevel="signature" />
-
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
@@ -43,23 +39,17 @@
         android:forceQueryable="true"
         android:supportsRtl="true">
 
-        <service
-            android:name=".CompanionDeviceDiscoveryService"
-            android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
-            android:exported="true">
-        </service>
-
         <activity
             android:name=".CompanionDeviceActivity"
-            android:theme="@style/ChooserActivity"
+            android:exported="true"
+            android:launchMode="singleInstance"
+            android:excludeFromRecents="true"
             android:permission="android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE"
-            android:exported="true">
-            <!--TODO include url scheme filter similar to PrintSpooler -->
-            <intent-filter>
-                <action android:name="android.companiondevice.START_DISCOVERY" />
-                <category android:name="android.intent.category.DEFAULT" />
-            </intent-filter>
-        </activity>
+            android:theme="@style/ChooserActivity"/>
+
+        <service
+            android:name=".CompanionDeviceDiscoveryService"
+            android:exported="false" />
 
     </application>
 
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
new file mode 100644
index 0000000..c87bac6
--- /dev/null
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -0,0 +1,81 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="@drawable/dialog_background"
+              android:elevation="16dp"
+              android:maxHeight="400dp"
+              android:orientation="vertical"
+              android:padding="18dp"
+              android:layout_gravity="center">
+
+    <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:paddingHorizontal="12dp"
+            style="@*android:style/TextAppearance.Widget.Toolbar.Title"/>
+    <!-- style="@*android:style/TextAppearance.Widget.Toolbar.Title" -->
+
+    <TextView
+            android:id="@+id/summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dp"
+            android:layout_marginBottom="12dp"
+            android:gravity="center"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="14sp" />
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1">
+
+        <ListView
+                android:id="@+id/device_list"
+                style="@android:style/Widget.Material.ListView"
+                android:layout_width="match_parent"
+                android:layout_height="200dp" />
+
+    </RelativeLayout>
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:gravity="end">
+
+        <Button
+                android:id="@+id/button_cancel"
+                style="@android:style/Widget.Material.Button.Borderless.Colored"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/consent_no"
+                android:textColor="?android:attr/textColorSecondary" />
+
+        <Button
+                android:id="@+id/button_allow"
+                style="@android:style/Widget.Material.Button.Borderless.Colored"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/consent_yes" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/buttons.xml b/packages/CompanionDeviceManager/res/layout/buttons.xml
deleted file mode 100644
index a80720c..0000000
--- a/packages/CompanionDeviceManager/res/layout/buttons.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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.
--->
-
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/buttons"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:layout_alignParentBottom="true"
-    android:layout_alignParentEnd="true"
-    android:gravity="end"
->
-    <Button
-        android:id="@+id/button_cancel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/consent_no"
-        android:textColor="?android:attr/textColorSecondary"
-        style="@android:style/Widget.Material.Button.Borderless.Colored"
-    />
-    <Button
-        android:id="@+id/button_pair"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/consent_yes"
-        style="@android:style/Widget.Material.Button.Borderless.Colored"
-    />
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/device_chooser.xml b/packages/CompanionDeviceManager/res/layout/device_chooser.xml
deleted file mode 100644
index 273347a..0000000
--- a/packages/CompanionDeviceManager/res/layout/device_chooser.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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.
--->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
-    android:layout_height="400dp"
-    style="@style/ContainerLayout"
-    >
-
-    <include layout="@layout/title" />
-
-    <include layout="@layout/profile_summary" />
-
-    <ListView
-        android:id="@+id/device_list"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_below="@+id/profile_summary"
-        android:layout_above="@+id/buttons"
-        style="@android:style/Widget.Material.ListView"
-    />
-
-    <include layout="@layout/buttons" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml b/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
deleted file mode 100644
index 1336e79..0000000
--- a/packages/CompanionDeviceManager/res/layout/device_confirmation.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/container"
-    android:layout_height="wrap_content"
-    style="@style/ContainerLayout"
-    >
-
-    <include layout="@layout/title" />
-
-    <include layout="@layout/profile_summary" />
-
-    <include layout="@layout/buttons" />
-
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/layout/title.xml b/packages/CompanionDeviceManager/res/layout/title.xml
deleted file mode 100644
index 9a50366..0000000
--- a/packages/CompanionDeviceManager/res/layout/title.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
--->
-
-
-<TextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/title"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:gravity="center"
-    style="@*android:style/TextAppearance.Widget.Toolbar.Title"
-/>
\ 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 3faed55..aec8f89 100644
--- a/packages/CompanionDeviceManager/res/values-af/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-af/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Metgeseltoestel-bestuurder"</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="profile_name_generic" msgid="6851028682723034988">"toestel"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"horlosie"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Laat &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toe om jou &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; te bestuur"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Hierdie program is nodig om jou <xliff:g id="PROFILE_NAME">%1$s</xliff:g> te bestuur. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"toestel"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Laat toe"</string>
     <string name="consent_no" msgid="2640796915611404382">"Moenie toelaat nie"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 99466d7..2359124 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"አጃቢ የመሣሪያ አስተዳዳሪ"</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="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; የእርስዎን <xliff:g id="DEVICE_NAME">%2$s</xliff:g> - &lt;strong&gt;&lt;/strong&gt; እንዲያስተዳደር ይፍቀዱ"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"የእርስዎን <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ለማስተዳደር ይህ መተግበሪያ ያስፈልጋል <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"መሣሪያ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ፍቀድ"</string>
     <string name="consent_no" msgid="2640796915611404382">"አትፍቀድ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index c3f1e73..5fc3a4f 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"تطبيق \"مدير الجهاز المصاحب\""</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="profile_name_generic" msgid="6851028682723034988">"جهاز"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ساعة"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"‏السماح للتطبيق &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_summary" msgid="2059360676631420073">"هذا التطبيق مطلوب لإدارة <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"جهاز"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"السماح"</string>
     <string name="consent_no" msgid="2640796915611404382">"عدم السماح"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-as/strings.xml b/packages/CompanionDeviceManager/res/values-as/strings.xml
index 2a2bc25..743d725 100644
--- a/packages/CompanionDeviceManager/res/values-as/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-as/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"কম্পেনিয়ন ডিভাইচ মেনেজাৰ"</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="profile_name_generic" msgid="6851028682723034988">"ডিভাইচ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ী"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"আপোনাৰ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> পৰিচালনা কৰিবলৈ এই এপ্‌টোৰ আৱশ্যক। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইচ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিয়ক"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি নিদিব"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-az/strings.xml b/packages/CompanionDeviceManager/res/values-az/strings.xml
index 2ec13c5..ca32052 100644
--- a/packages/CompanionDeviceManager/res/values-az/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-az/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kompanyon Cihaz Meneceri"</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="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"izləyin"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tətbiqinə &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazınızı idarə etməsinə icazə verin"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Bu tətbiq <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilinizi idarə etmək üçün lazımdır. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"İcazə verin"</string>
     <string name="consent_no" msgid="2640796915611404382">"İcazə verməyin"</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 d687b05..d919e67 100644
--- a/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-b+sr+Latn/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Menadžer pridruženog uređaja"</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="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ova aplikacija je potrebna za upravljanje profilom <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dozvoli"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-be/strings.xml b/packages/CompanionDeviceManager/res/values-be/strings.xml
index 2236052f5..919d729 100644
--- a/packages/CompanionDeviceManager/res/values-be/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-be/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Менеджар спадарожнай прылады"</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="profile_name_generic" msgid="6851028682723034988">"прылада"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"гадзіннік"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Дазвольце праграме &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_summary" msgid="2059360676631420073">"Гэта праграма неабходная для кіравання профілем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"прылада"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дазволіць"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дазваляць"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bg/strings.xml b/packages/CompanionDeviceManager/res/values-bg/strings.xml
index 996ca90..1e2aa4e 100644
--- a/packages/CompanionDeviceManager/res/values-bg/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bg/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Разрешаване на &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_summary" msgid="2059360676631420073">"Това приложение е необходимо за управление на <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешаване"</string>
     <string name="consent_no" msgid="2640796915611404382">"Забраняване"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bn/strings.xml b/packages/CompanionDeviceManager/res/values-bn/strings.xml
index 16d25ce..3b537b6 100644
--- a/packages/CompanionDeviceManager/res/values-bn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bn/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ঘড়ি"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"আপনার &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_summary" msgid="2059360676631420073">"আপনার <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ম্যানেজ করতে এই অ্যাপটি প্রয়োজন। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ডিভাইস"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"অনুমতি দিন"</string>
     <string name="consent_no" msgid="2640796915611404382">"অনুমতি দেবেন না"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-bs/strings.xml b/packages/CompanionDeviceManager/res/values-bs/strings.xml
index 10f753c..b010626 100644
--- a/packages/CompanionDeviceManager/res/values-bs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-bs/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Prateći upravitelj uređaja"</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="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"sat"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Dozvolite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja uređajem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ova aplikacija je potrebna za upravljanje profilom: <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dozvoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dozvoliti"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ca/strings.xml b/packages/CompanionDeviceManager/res/values-ca/strings.xml
index e55a033..efd801e 100644
--- a/packages/CompanionDeviceManager/res/values-ca/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ca/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestor de dispositius complementaris"</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="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"rellotge"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permet que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gestioni el teu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Aquesta aplicació es necessita per gestionar el teu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositiu"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permet"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permetis"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-cs/strings.xml b/packages/CompanionDeviceManager/res/values-cs/strings.xml
index 48fbda1..bd57213 100644
--- a/packages/CompanionDeviceManager/res/values-cs/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-cs/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Správce doprovodných zařízení"</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="profile_name_generic" msgid="6851028682723034988">"zařízení"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Povolit aplikaci &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; spravovat &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Tato aplikace je nutná pro správu profilu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"zařízení"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Povolit"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovolovat"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-da/strings.xml b/packages/CompanionDeviceManager/res/values-da/strings.xml
index 446c301..7428453 100644
--- a/packages/CompanionDeviceManager/res/values-da/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-da/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Medfølgende enhedsadministrator"</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="profile_name_generic" msgid="6851028682723034988">"enhed"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ur"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Tillad at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; kan administrere: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Du skal bruge denne app for at administrere dit <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"enhed"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillad"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillad ikke"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-de/strings.xml b/packages/CompanionDeviceManager/res/values-de/strings.xml
index 33d831d..4c43140 100644
--- a/packages/CompanionDeviceManager/res/values-de/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-de/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Begleitgerät-Manager"</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="profile_name_generic" msgid="6851028682723034988">"Gerät"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"Smartwatch"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; erlauben, dein Gerät &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; zu verwalten"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Diese App wird zur Verwaltung des Profils „<xliff:g id="PROFILE_NAME">%1$s</xliff:g>“ benötigt. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"Gerät"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Zulassen"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nicht zulassen"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-el/strings.xml b/packages/CompanionDeviceManager/res/values-el/strings.xml
index 7a78c06..07a4fda 100644
--- a/packages/CompanionDeviceManager/res/values-el/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-el/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Διαχείριση συνοδευτικής εφαρμογής"</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="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ρολόι"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Επιτρέψτε στην εφαρμογή &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_summary" msgid="2059360676631420073">"Αυτή η εφαρμογή είναι απαραίτητη για τη διαχείριση του προφίλ σας <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"συσκευή"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Να επιτρέπεται"</string>
     <string name="consent_no" msgid="2640796915611404382">"Να μην επιτρέπεται"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
index a6ebe65..1756d22 100644
--- a/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rAU/strings.xml
@@ -17,11 +17,19 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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" product="default" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="summary_watch" product="tablet" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="title_app_streaming" msgid="4459136600249308574">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to stream applications?"</string>
+    <string name="summary_app_streaming" product="default" msgid="6105916810614498138">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected."</string>
+    <string name="summary_app_streaming" product="tablet" msgid="2996373715966272792">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this tablet when connected."</string>
+    <string name="summary_app_streaming" product="device" msgid="7614171699434639963">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected."</string>
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></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>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
index a6ebe65..1756d22 100644
--- a/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rCA/strings.xml
@@ -17,11 +17,19 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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" product="default" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="summary_watch" product="tablet" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="title_app_streaming" msgid="4459136600249308574">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to stream applications?"</string>
+    <string name="summary_app_streaming" product="default" msgid="6105916810614498138">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected."</string>
+    <string name="summary_app_streaming" product="tablet" msgid="2996373715966272792">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this tablet when connected."</string>
+    <string name="summary_app_streaming" product="device" msgid="7614171699434639963">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected."</string>
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></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>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
index a6ebe65..1756d22 100644
--- a/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rGB/strings.xml
@@ -17,11 +17,19 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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" product="default" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="summary_watch" product="tablet" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="title_app_streaming" msgid="4459136600249308574">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to stream applications?"</string>
+    <string name="summary_app_streaming" product="default" msgid="6105916810614498138">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected."</string>
+    <string name="summary_app_streaming" product="tablet" msgid="2996373715966272792">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this tablet when connected."</string>
+    <string name="summary_app_streaming" product="device" msgid="7614171699434639963">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected."</string>
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></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>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
index a6ebe65..1756d22 100644
--- a/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rIN/strings.xml
@@ -17,11 +17,19 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"watch"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to manage your &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"This app is needed to manage your <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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" product="default" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="summary_watch" product="tablet" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+    <string name="title_app_streaming" msgid="4459136600249308574">"Allow &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; to stream applications?"</string>
+    <string name="summary_app_streaming" product="default" msgid="6105916810614498138">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected."</string>
+    <string name="summary_app_streaming" product="tablet" msgid="2996373715966272792">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this tablet when connected."</string>
+    <string name="summary_app_streaming" product="device" msgid="7614171699434639963">"Let &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; provide &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected."</string>
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></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>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
index 6cc56a4..efda04e 100644
--- a/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-en-rXC/strings.xml
@@ -17,11 +17,19 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎Companion Device Manager‎‏‎‎‏‎"</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="profile_name_generic" msgid="6851028682723034988">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎device‎‏‎‎‏‎"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎watch‎‏‎‎‏‎"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to manage your &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt;‎‏‎‎‏‎"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‎‏‎This app is needed to manage your ‎‏‎‎‏‏‎<xliff:g id="PROFILE_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎. ‎‏‎‎‏‏‎<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</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" product="default" msgid="7113724443198337683">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.‎‏‎‎‏‎"</string>
+    <string name="summary_watch" product="tablet" msgid="7113724443198337683">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.‎‏‎‎‏‎"</string>
+    <string name="title_app_streaming" msgid="4459136600249308574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‏‎‎Allow &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to stream applications?‎‏‎‎‏‎"</string>
+    <string name="summary_app_streaming" product="default" msgid="6105916810614498138">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎Let &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to provide &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; remote access to access to applications installed on this phone when connected.‎‏‎‎‏‎"</string>
+    <string name="summary_app_streaming" product="tablet" msgid="2996373715966272792">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‎Let &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to provide &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; remote access to access to applications installed on this tablet when connected.‎‏‎‎‏‎"</string>
+    <string name="summary_app_streaming" product="device" msgid="7614171699434639963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‏‏‎Let &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; to provide &lt;strong&gt;‎‏‎‎‏‏‎<xliff:g id="DEVICE_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/strong&gt; remote access to access to applications installed on this device when connected.‎‏‎‎‏‎"</string>
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></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>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
index dc13159..9bbc1b8 100644
--- a/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es-rUS/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Administrador de dispositivo complementario"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Elige un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para que &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; lo administre"</string>
-    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permite que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administre tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Esta app es necesaria para administrar tu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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 &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt; lo administre"</string>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-es/strings.xml b/packages/CompanionDeviceManager/res/values-es/strings.xml
index 7e37c1d..daece56 100644
--- a/packages/CompanionDeviceManager/res/values-es/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-es/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestor de dispositivos complementario"</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="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"reloj"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gestione tu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Se necesita esta aplicación para gestionar tu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"No permitir"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-et/strings.xml b/packages/CompanionDeviceManager/res/values-et/strings.xml
index 399556d..ffaa0c0 100644
--- a/packages/CompanionDeviceManager/res/values-et/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-et/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kaasseadme haldur"</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="profile_name_generic" msgid="6851028682723034988">"seade"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"käekell"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Lubage rakendusel &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hallata teie seadet &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Seda rakendust on vaja teie profiili <xliff:g id="PROFILE_NAME">%1$s</xliff:g> haldamiseks. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"seade"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Luba"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ära luba"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 764505e..5bf6677 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gailu osagarriaren kudeatzailea"</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="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Eman &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; kudeatzeko baimena &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; aplikazioari"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Aplikazioa <xliff:g id="PROFILE_NAME">%1$s</xliff:g> kudeatzeko beharrezkoa da. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"gailua"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Eman baimena"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ez eman baimenik"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fa/strings.xml b/packages/CompanionDeviceManager/res/values-fa/strings.xml
index 07d04aa..1ede28c 100644
--- a/packages/CompanionDeviceManager/res/values-fa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fa/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"مدیر دستگاه مرتبط"</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="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ساعت"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"‏مجاز کردن &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_summary" msgid="2059360676631420073">"این برنامه برای مدیریت <xliff:g id="PROFILE_NAME">%1$s</xliff:g> شما لازم است. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"دستگاه"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"مجاز بودن"</string>
     <string name="consent_no" msgid="2640796915611404382">"مجاز نبودن"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fi/strings.xml b/packages/CompanionDeviceManager/res/values-fi/strings.xml
index 528d16c..ac948df 100644
--- a/packages/CompanionDeviceManager/res/values-fi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fi/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"laite"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"kello"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Salli, että &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; voi hallinnoida tätä laitettasi: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Profiilin (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) ylläpitoon tarvitaan tätä sovellusta. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"laite"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Salli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Älä salli"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
index 1dcd337..19f97f0 100644
--- a/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr-rCA/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestionnaire d\'appareil compagnon"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Cette application est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+    <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
+    <string name="chooser_title" msgid="2262294130493605839">"Choisissez un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> qui sera géré par &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml
index ba2fc8e..8a7ae1a 100644
--- a/packages/CompanionDeviceManager/res/values-fr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestionnaire d\'appareils associés"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Sélectionner 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="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Autoriser &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; à gérer votre &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Cette appli est nécessaire pour gérer votre <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+    <string name="profile_name_watch" msgid="576290739483672360">"montre"</string>
+    <string name="chooser_title" msgid="2262294130493605839">"Sélectionner 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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Autoriser"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne pas autoriser"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gl/strings.xml b/packages/CompanionDeviceManager/res/values-gl/strings.xml
index 5f9a8d7..052c207 100644
--- a/packages/CompanionDeviceManager/res/values-gl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gl/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Xestor de dispositivos complementarios"</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="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"reloxo"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permitir que &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; xestione o teu dispositivo (&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;)"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Esta aplicación é necesaria para xestionar o teu perfil (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>). <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non permitir"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 71cf012..279de16 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"કમ્પેનિયન ડિવાઇસ મેનેજર"</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="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"તમારા &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_summary" msgid="2059360676631420073">"તમારી <xliff:g id="PROFILE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે આ ઍપ જરૂરી છે. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ડિવાઇસ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"મંજૂરી આપો"</string>
     <string name="consent_no" msgid="2640796915611404382">"મંજૂરી આપશો નહીં"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hi/strings.xml b/packages/CompanionDeviceManager/res/values-hi/strings.xml
index d4dd1cb..7704829 100644
--- a/packages/CompanionDeviceManager/res/values-hi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hi/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"सहयोगी डिवाइस मैनेजर"</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="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"स्मार्टवॉच"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"<xliff:g id="PROFILE_NAME">%1$s</xliff:g> को मैनेज करने के लिए, यह ऐप्लिकेशन ज़रूरी है. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"डिवाइस"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दें"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति न दें"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hr/strings.xml b/packages/CompanionDeviceManager/res/values-hr/strings.xml
index 87c5ae2..e7db2ba 100644
--- a/packages/CompanionDeviceManager/res/values-hr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hr/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"satom"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Dopustite aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; da upravlja vašim &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ta je aplikacija potrebna za upravljanje vašim profilom <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"uređaj"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dopusti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nemoj dopustiti"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml
index c7ceb38..56f02a5 100644
--- a/packages/CompanionDeviceManager/res/values-hu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Társeszközök kezelője"</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="profile_name_generic" msgid="6851028682723034988">"eszköz"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"óra"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"A(z) &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; engedélyezése a(z) &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; kezelésére"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Szükség van erre az alkalmazásra a következő kezeléséhez: <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"eszköz"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Engedélyezés"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tiltás"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-hy/strings.xml b/packages/CompanionDeviceManager/res/values-hy/strings.xml
index 26f7990..cf22fbc 100644
--- a/packages/CompanionDeviceManager/res/values-hy/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-hy/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"սարք"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ժամացույց"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Թույլատրեք &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_summary" msgid="2059360676631420073">"Այս հավելվածն անհրաժեշտ է <xliff:g id="PROFILE_NAME">%1$s</xliff:g> սարքը կամ պրոֆիլը կառավարելու համար։ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"սարք"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Թույլատրել"</string>
     <string name="consent_no" msgid="2640796915611404382">"Չթույլատրել"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-in/strings.xml b/packages/CompanionDeviceManager/res/values-in/strings.xml
index b0618d4..41f1d09 100644
--- a/packages/CompanionDeviceManager/res/values-in/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-in/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Pengelola Perangkat Pendamping"</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="profile_name_generic" msgid="6851028682723034988">"perangkat"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"smartwatch"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Izinkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengelola &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Aplikasi ini diperlukan untuk mengelola <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"perangkat"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Izinkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan izinkan"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-is/strings.xml b/packages/CompanionDeviceManager/res/values-is/strings.xml
index b7d7c6a..5376912 100644
--- a/packages/CompanionDeviceManager/res/values-is/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-is/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Stjórnun fylgdartækja"</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="profile_name_generic" msgid="6851028682723034988">"tæki"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"úr"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Veita &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; stjórn á: &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Þetta forrit er nauðsynlegt til að hafa umsjón með <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"tæki"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Leyfa"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ekki leyfa"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-it/strings.xml b/packages/CompanionDeviceManager/res/values-it/strings.xml
index ce003e7..af9e8ca 100644
--- a/packages/CompanionDeviceManager/res/values-it/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-it/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestione dispositivi companion"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Scegli un <xliff:g id="PROFILE_NAME">%1$s</xliff:g> che sia gestito da &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"orologio"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Consenti all\'app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; di gestire &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Questa app è necessaria per gestire il tuo <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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> che sia gestito da &lt;strong&gt;<xliff:g id="APP_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Consenti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Non consentire"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-iw/strings.xml b/packages/CompanionDeviceManager/res/values-iw/strings.xml
index 54c523c..68ca9d9 100644
--- a/packages/CompanionDeviceManager/res/values-iw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-iw/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ניהול מכשיר מותאם"</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="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"שעון"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"‏אישור לאפליקציה &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_summary" msgid="2059360676631420073">"האפליקציה הזו נחוצה כדי לנהל את ה<xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"מכשיר"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"יש אישור"</string>
     <string name="consent_no" msgid="2640796915611404382">"אין אישור"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ja/strings.xml b/packages/CompanionDeviceManager/res/values-ja/strings.xml
index f92fafe..c10a1e1 100644
--- a/packages/CompanionDeviceManager/res/values-ja/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ja/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"コンパニオン デバイス マネージャ"</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="profile_name_generic" msgid="6851028682723034988">"デバイス"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ウォッチ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"このアプリは <xliff:g id="PROFILE_NAME">%1$s</xliff:g> の管理に必要です。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"デバイス"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"許可"</string>
     <string name="consent_no" msgid="2640796915611404382">"許可しない"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ka/strings.xml b/packages/CompanionDeviceManager/res/values-ka/strings.xml
index 34efdd2..6372481 100644
--- a/packages/CompanionDeviceManager/res/values-ka/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ka/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"კომპანიონი მოწყობილობების მენეჯერი"</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="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"საათი"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"ნება დართეთ &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_summary" msgid="2059360676631420073">"ეს აპი საჭიროა თქვენი <xliff:g id="PROFILE_NAME">%1$s</xliff:g>-ს სამართავად. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"მოწყობილობა"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"დაშვება"</string>
     <string name="consent_no" msgid="2640796915611404382">"არ დაიშვას"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kk/strings.xml b/packages/CompanionDeviceManager/res/values-kk/strings.xml
index 3c7f697..6ac9c04 100644
--- a/packages/CompanionDeviceManager/res/values-kk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kk/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"сағат"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"Бұл қолданба <xliff:g id="PROFILE_NAME">%1$s</xliff:g> профиліңізді басқару үшін қажет. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"құрылғы"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Рұқсат беру"</string>
     <string name="consent_no" msgid="2640796915611404382">"Рұқсат бермеу"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-km/strings.xml b/packages/CompanionDeviceManager/res/values-km/strings.xml
index 74ccd84..db2634f 100644
--- a/packages/CompanionDeviceManager/res/values-km/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-km/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"កម្មវិធី​គ្រប់​គ្រង​ឧបករណ៍ដៃគូ"</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="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"នាឡិកា"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"អនុញ្ញាតឱ្យ &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_summary" msgid="2059360676631420073">"ត្រូវការកម្មវិធីនេះ ដើម្បីគ្រប់គ្រង <xliff:g id="PROFILE_NAME">%1$s</xliff:g> របស់អ្នក។ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ឧបករណ៍"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"អនុញ្ញាត"</string>
     <string name="consent_no" msgid="2640796915611404382">"កុំអនុញ្ញាត"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-kn/strings.xml b/packages/CompanionDeviceManager/res/values-kn/strings.xml
index 2a82d1f..e6413da 100644
--- a/packages/CompanionDeviceManager/res/values-kn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-kn/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ಕಂಪ್ಯಾನಿಯನ್ ಸಾಧನ ನಿರ್ವಾಹಕರು"</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="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ವೀಕ್ಷಿಸಿ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"ನಿಮ್ಮ &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_summary" msgid="2059360676631420073">"ನಿಮ್ಮ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. ಅನ್ನು ನಿರ್ವಹಿಸಲು ಈ ಆ್ಯಪ್‌ನ ಅಗತ್ಯವಿದೆ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ಸಾಧನ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ಅನುಮತಿಸಿ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ಅನುಮತಿಸಬೇಡಿ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ko/strings.xml b/packages/CompanionDeviceManager/res/values-ko/strings.xml
index 6ca01bf..02459d5 100644
--- a/packages/CompanionDeviceManager/res/values-ko/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ko/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"부속 기기 관리자"</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="profile_name_generic" msgid="6851028682723034988">"기기"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"시계"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"이 앱은 <xliff:g id="PROFILE_NAME">%1$s</xliff:g> 프로필을 관리하는 데 필요합니다. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"기기"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"허용"</string>
     <string name="consent_no" msgid="2640796915611404382">"허용 안함"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml
index 18d38a8..ea4230a 100644
--- a/packages/CompanionDeviceManager/res/values-ky/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"саат"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"Бул колдонмо <xliff:g id="PROFILE_NAME">%1$s</xliff:g> профилиңизди башкаруу үчүн керек. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"түзмөк"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Уруксат берүү"</string>
     <string name="consent_no" msgid="2640796915611404382">"Уруксат берилбесин"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lo/strings.xml b/packages/CompanionDeviceManager/res/values-lo/strings.xml
index a1eb713..b6c6289 100644
--- a/packages/CompanionDeviceManager/res/values-lo/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lo/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ຕົວຈັດການອຸປະກອນປະກອບ"</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="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ໂມງ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"ອະນຸຍາດໃຫ້ &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_summary" msgid="2059360676631420073">"ຕ້ອງໃຊ້ແອັບນີ້ເພື່ອຈັດການ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ຂອງທ່ານ. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ອຸປະກອນ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ອະນຸຍາດ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ບໍ່ອະນຸຍາດ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lt/strings.xml b/packages/CompanionDeviceManager/res/values-lt/strings.xml
index 65f371d..e5ff480 100644
--- a/packages/CompanionDeviceManager/res/values-lt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lt/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"laikrodį"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Leisti &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; tvarkyti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ši programa reikalinga norint tvarkyti jūsų <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"įrenginys"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Leisti"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neleisti"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-lv/strings.xml b/packages/CompanionDeviceManager/res/values-lv/strings.xml
index b18bfe4..d521240 100644
--- a/packages/CompanionDeviceManager/res/values-lv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-lv/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Palīgierīču pārzinis"</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="profile_name_generic" msgid="6851028682723034988">"ierīce"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"pulkstenis"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Atļauja lietotnei &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; pārvaldīt ierīci &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Šī lietotne ir nepieciešama jūsu profila (<xliff:g id="PROFILE_NAME">%1$s</xliff:g>) pārvaldībai. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ierīce"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Atļaut"</string>
     <string name="consent_no" msgid="2640796915611404382">"Neatļaut"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml
index 9d745c4..e6131e6 100644
--- a/packages/CompanionDeviceManager/res/values-mk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml
@@ -17,11 +17,19 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"уред"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"часовник"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Дозволете &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_summary" msgid="2059360676631420073">"Апликацијава е потребна за управување со вашиот <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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" product="default" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за телефонот, SMS, контактите и календарот."</string>
+    <string name="summary_watch" product="tablet" msgid="7113724443198337683">"<xliff:g id="APP_NAME">%1$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за телефонот, SMS, контактите и календарот."</string>
+    <string name="title_app_streaming" msgid="4459136600249308574">"Да се дозволи &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; да стримува апликации?"</string>
+    <string name="summary_app_streaming" product="default" msgid="6105916810614498138">"Дозволете &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="summary_app_streaming" product="tablet" msgid="2996373715966272792">"Дозволете &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="summary_app_streaming" product="device" msgid="7614171699434639963">"Дозволете &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="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"уред"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволувај"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ml/strings.xml b/packages/CompanionDeviceManager/res/values-ml/strings.xml
index 28c88da..639909a 100644
--- a/packages/CompanionDeviceManager/res/values-ml/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ml/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"കമ്പാനിയൻ ഉപകരണ മാനേജർ"</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="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"വാച്ച്"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"നിങ്ങളുടെ &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_summary" msgid="2059360676631420073">"നിങ്ങളുടെ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> മാനേജ് ചെയ്യാൻ ഈ ആപ്പ് ആവശ്യമാണ്. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ഉപകരണം"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"അനുവദിക്കുക"</string>
     <string name="consent_no" msgid="2640796915611404382">"അനുവദിക്കരുത്"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mn/strings.xml b/packages/CompanionDeviceManager/res/values-mn/strings.xml
index 11e61d9..adbe62d 100644
--- a/packages/CompanionDeviceManager/res/values-mn/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mn/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"цаг"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"Энэ апп таны <xliff:g id="PROFILE_NAME">%1$s</xliff:g>-г удирдахад шаардлагатай. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"төхөөрөмж"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Зөвшөөрөх"</string>
     <string name="consent_no" msgid="2640796915611404382">"Бүү зөвшөөр"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-mr/strings.xml b/packages/CompanionDeviceManager/res/values-mr/strings.xml
index c73ed28..fce0583 100644
--- a/packages/CompanionDeviceManager/res/values-mr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-mr/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"सहयोगी डिव्हाइस व्यवस्थापक"</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="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"वॉच"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"तुमचे &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_summary" msgid="2059360676631420073">"तुमची <xliff:g id="PROFILE_NAME">%1$s</xliff:g> प्रोफाइल व्यवस्थापित करण्यासाठी हे ॲप आवश्यक आहे. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"डिव्हाइस"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमती द्या"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमती देऊ नका"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ms/strings.xml b/packages/CompanionDeviceManager/res/values-ms/strings.xml
index d2aebb4..5c4ec78 100644
--- a/packages/CompanionDeviceManager/res/values-ms/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ms/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Pengurus Peranti Rakan"</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="profile_name_generic" msgid="6851028682723034988">"peranti"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"jam tangan"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Benarkan &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; untuk mengurus &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; anda"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Apl ini diperlukan untuk mengurus <xliff:g id="PROFILE_NAME">%1$s</xliff:g> anda. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"peranti"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Benarkan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Jangan benarkan"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 45c9d8b..f3e572e 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"တွဲဖက်ကိရိယာ မန်နေဂျာ"</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="profile_name_generic" msgid="6851028682723034988">"စက်"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"သင်၏ &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_summary" msgid="2059360676631420073">"သင်၏ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်ကိုလိုအပ်သည်။ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"စက်"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ခွင့်ပြုရန်"</string>
     <string name="consent_no" msgid="2640796915611404382">"ခွင့်မပြုပါ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nb/strings.xml b/packages/CompanionDeviceManager/res/values-nb/strings.xml
index af1ffe9..d3eb7e5 100644
--- a/packages/CompanionDeviceManager/res/values-nb/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nb/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"klokke"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Tillat at &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; administrerer &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Denne appen kreves for å administrere <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ikke tillat"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ne/strings.xml b/packages/CompanionDeviceManager/res/values-ne/strings.xml
index b29f94c..9bcf69b 100644
--- a/packages/CompanionDeviceManager/res/values-ne/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ne/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"सहयोगी डिभाइसको प्रबन्धक"</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="profile_name_generic" msgid="6851028682723034988">"यन्त्र"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"घडी"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"आफ्नो &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_summary" msgid="2059360676631420073">"तपाईंको <xliff:g id="PROFILE_NAME">%1$s</xliff:g> व्यवस्थापन गर्न यो एपलाई अनुमति दिनु पर्ने हुन्छ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"यन्त्र"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"अनुमति दिनुहोस्"</string>
     <string name="consent_no" msgid="2640796915611404382">"अनुमति नदिनुहोस्"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-nl/strings.xml b/packages/CompanionDeviceManager/res/values-nl/strings.xml
index a56fb9a..9ee09db 100644
--- a/packages/CompanionDeviceManager/res/values-nl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-nl/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"apparaat"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"horloge"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; toestaan je &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; te beheren"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Deze app is vereist om je <xliff:g id="PROFILE_NAME">%1$s</xliff:g> te beheren. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"apparaat"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Toestaan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Niet toestaan"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-or/strings.xml b/packages/CompanionDeviceManager/res/values-or/strings.xml
index 8e43213..e08ec28 100644
--- a/packages/CompanionDeviceManager/res/values-or/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-or/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ସହଯୋଗୀ ଡିଭାଇସ୍ ପରିଚାଳକ"</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="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ୱାଚ୍"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"ଆପଣଙ୍କ &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_summary" msgid="2059360676631420073">"ଆପଣଙ୍କ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>କୁ ପରିଚାଳନା କରିବା ପାଇଁ ଏହି ଆପ୍ ଆବଶ୍ୟକ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ଡିଭାଇସ୍"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pa/strings.xml b/packages/CompanionDeviceManager/res/values-pa/strings.xml
index 54f4f8c..e317a464f 100644
--- a/packages/CompanionDeviceManager/res/values-pa/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pa/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ਸੰਬੰਧੀ ਡੀਵਾਈਸ ਪ੍ਰਬੰਧਕ"</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="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ਸਮਾਰਟ-ਵਾਚ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"ਤੁਹਾਡੇ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਲਈ ਇਹ ਐਪ ਲੋੜੀਂਦੀ ਹੈ। <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"ਡੀਵਾਈਸ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ਇਜਾਜ਼ਤ ਦਿਓ"</string>
     <string name="consent_no" msgid="2640796915611404382">"ਇਜਾਜ਼ਤ ਨਾ ਦਿਓ"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pl/strings.xml b/packages/CompanionDeviceManager/res/values-pl/strings.xml
index a989baa..6cb7cc6 100644
--- a/packages/CompanionDeviceManager/res/values-pl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pl/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Menedżer urządzeń towarzyszących"</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="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"zegarek"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Zezwól na zarządzanie urządzeniem &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; przez aplikację &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ta aplikacja jest niezbędna do zarządzania profilem <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"urządzenie"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Zezwól"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nie zezwalaj"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index d2724c0..4306286 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gerenciador de dispositivos complementar"</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="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gerencie seu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Esse app é necessário para gerenciar seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
index 2f5a53b..8f05d49 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rPT/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gestor de dispositivos associados"</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="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permita que a app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; faça a gestão do seu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Esta app é necessária para gerir o seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index d2724c0..4306286 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Gerenciador de dispositivos complementar"</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="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permitir que o app &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; gerencie seu &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Esse app é necessário para gerenciar seu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispositivo"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permitir"</string>
     <string name="consent_no" msgid="2640796915611404382">"Não permitir"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ro/strings.xml b/packages/CompanionDeviceManager/res/values-ro/strings.xml
index 4df74de..43a4de7 100644
--- a/packages/CompanionDeviceManager/res/values-ro/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ro/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Manager de dispozitiv Companion"</string>
-    <string name="chooser_title" msgid="2262294130493605839">"Alegeți 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="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Permiteți ca &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; să vă gestioneze dispozitivul &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Această aplicație este necesară pentru a gestiona <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</string>
+    <string name="profile_name_watch" msgid="576290739483672360">"ceas"</string>
+    <string name="chooser_title" msgid="2262294130493605839">"Alegeți 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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"dispozitiv"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Permiteți"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nu permiteți"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ru/strings.xml b/packages/CompanionDeviceManager/res/values-ru/strings.xml
index ea372d5..6d5c0de 100644
--- a/packages/CompanionDeviceManager/res/values-ru/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ru/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Управление подключенными устройствами"</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="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"часы"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Разрешите приложению &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_summary" msgid="2059360676631420073">"Это приложение необходимо для управления вашим профилем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"устройство"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Разрешить"</string>
     <string name="consent_no" msgid="2640796915611404382">"Запретить"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-si/strings.xml b/packages/CompanionDeviceManager/res/values-si/strings.xml
index a5c2c88..b4e28d8 100644
--- a/packages/CompanionDeviceManager/res/values-si/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-si/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"සහායක උපාංග කළමනාකරු"</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="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ඔරලෝසුව"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&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_summary" msgid="2059360676631420073">"මෙම යෙදුමට ඔබගේ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> කළමනාකරණය කිරීමට අවශ්‍යයි. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"උපාංගය"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"ඉඩ දෙන්න"</string>
     <string name="consent_no" msgid="2640796915611404382">"ඉඩ නොදෙන්න"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sk/strings.xml b/packages/CompanionDeviceManager/res/values-sk/strings.xml
index a9bf77f..4f86f08 100644
--- a/packages/CompanionDeviceManager/res/values-sk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sk/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Správca sprievodných zariadení"</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="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"hodinky"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Povoliť aplikácii &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; spravovať zariadenie &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Táto aplikácia sa vyžaduje na správu profilu <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"zariadenie"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Povoliť"</string>
     <string name="consent_no" msgid="2640796915611404382">"Nepovoliť"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sl/strings.xml b/packages/CompanionDeviceManager/res/values-sl/strings.xml
index 4eb8f50..a54af21 100644
--- a/packages/CompanionDeviceManager/res/values-sl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sl/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Upravitelj spremljevalnih naprav"</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="profile_name_generic" msgid="6851028682723034988">"naprava"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ura"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Aplikaciji &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; dovolite upravljanje naprave &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ta aplikacija je potrebna za upravljanje profila »<xliff:g id="PROFILE_NAME">%1$s</xliff:g>«. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"naprava"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Dovoli"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ne dovoli"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sq/strings.xml b/packages/CompanionDeviceManager/res/values-sq/strings.xml
index 34357b4..d3f97df 100644
--- a/packages/CompanionDeviceManager/res/values-sq/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sq/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Menaxheri i pajisjes shoqëruese"</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="profile_name_generic" msgid="6851028682723034988">"pajisja"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"ora inteligjente"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Lejo që &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; të menaxhojë pajisjen tënde &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Ky aplikacion nevojitet për të menaxhuar profilin tënd <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"pajisja"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Lejo"</string>
     <string name="consent_no" msgid="2640796915611404382">"Mos lejo"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sr/strings.xml b/packages/CompanionDeviceManager/res/values-sr/strings.xml
index 37af185..db8f291 100644
--- a/packages/CompanionDeviceManager/res/values-sr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sr/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Менаџер придруженог уређаја"</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="profile_name_generic" msgid="6851028682723034988">"уређај"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"сат"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Дозволите апликацији &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_summary" msgid="2059360676631420073">"Ова апликација је потребна за управљање профилом <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"уређај"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволи"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволи"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sv/strings.xml b/packages/CompanionDeviceManager/res/values-sv/strings.xml
index f78fadf..733b2f7 100644
--- a/packages/CompanionDeviceManager/res/values-sv/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sv/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"klocka"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Tillåt att &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; hanterar din &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Appen behövs för att hantera <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"enhet"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Tillåt"</string>
     <string name="consent_no" msgid="2640796915611404382">"Tillåt inte"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index 495c441..02db256 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kidhibiti cha Vifaa Visaidizi"</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="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Ruhusu &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; idhibiti &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; yako"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Programu hii inahitajika ili udhibiti wasifu wako wa <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"kifaa"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruhusu"</string>
     <string name="consent_no" msgid="2640796915611404382">"Usiruhusu"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ta/strings.xml b/packages/CompanionDeviceManager/res/values-ta/strings.xml
index 20845bd..3e998c8 100644
--- a/packages/CompanionDeviceManager/res/values-ta/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ta/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"கம்பேனியன் சாதன நிர்வாகி"</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="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"வாட்ச்"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"உங்கள் &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_summary" msgid="2059360676631420073">"உங்கள் <xliff:g id="PROFILE_NAME">%1$s</xliff:g> சுயவிவரத்தை நிர்வகிக்க இந்த ஆப்ஸ் தேவைப்படுகிறது. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"சாதனம்"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"அனுமதி"</string>
     <string name="consent_no" msgid="2640796915611404382">"அனுமதிக்க வேண்டாம்"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-te/strings.xml b/packages/CompanionDeviceManager/res/values-te/strings.xml
index c855cf2..8c53126 100644
--- a/packages/CompanionDeviceManager/res/values-te/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-te/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"సహచర పరికర మేనేజర్"</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="profile_name_generic" msgid="6851028682723034988">"పరికరం"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"వాచ్"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"మీ &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_summary" msgid="2059360676631420073">"మీ <xliff:g id="PROFILE_NAME">%1$s</xliff:g>ను మేనేజ్ చేయడానికి ఈ యాప్ అవసరం. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"పరికరం"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"అనుమతించు"</string>
     <string name="consent_no" msgid="2640796915611404382">"అనుమతించవద్దు"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-th/strings.xml b/packages/CompanionDeviceManager/res/values-th/strings.xml
index d78ada2..504731e 100644
--- a/packages/CompanionDeviceManager/res/values-th/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-th/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"นาฬิกา"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"อนุญาตให้ &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_summary" msgid="2059360676631420073">"ต้องใช้แอปนี้ในการจัดการ<xliff:g id="PROFILE_NAME">%1$s</xliff:g>ของคุณ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"อุปกรณ์"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"อนุญาต"</string>
     <string name="consent_no" msgid="2640796915611404382">"ไม่อนุญาต"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tl/strings.xml b/packages/CompanionDeviceManager/res/values-tl/strings.xml
index 03165b4..30d5e57 100644
--- a/packages/CompanionDeviceManager/res/values-tl/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tl/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Kasamang Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"device"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"relo"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Payagan ang &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; na pamahalaan ang iyong &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt;"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Kinakailangan ang app na ito para pamahalaan ang iyong <xliff:g id="PROFILE_NAME">%1$s</xliff:g>. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"device"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Payagan"</string>
     <string name="consent_no" msgid="2640796915611404382">"Huwag payagan"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml
index b2c1cf2..1b1d71d 100644
--- a/packages/CompanionDeviceManager/res/values-tr/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"saat"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; uygulaması &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; cihazınızı yönetebilsin mi?"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Bu uygulama, <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilinizin yönetilmesi için gereklidir. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"cihaz"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"İzin ver"</string>
     <string name="consent_no" msgid="2640796915611404382">"İzin verme"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 61b78e9..149841a 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Диспетчер супутніх пристроїв"</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="profile_name_generic" msgid="6851028682723034988">"пристрій"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Дозволити додатку &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_summary" msgid="2059360676631420073">"Цей додаток потрібен, щоб керувати профілем \"<xliff:g id="PROFILE_NAME">%1$s</xliff:g>\". <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"пристрій"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Дозволити"</string>
     <string name="consent_no" msgid="2640796915611404382">"Не дозволяти"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-ur/strings.xml b/packages/CompanionDeviceManager/res/values-ur/strings.xml
index ee79921..b467550 100644
--- a/packages/CompanionDeviceManager/res/values-ur/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ur/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"ساتھی آلہ مینیجر"</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="profile_name_generic" msgid="6851028682723034988">"آلہ"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"دیکھیں"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"‏اپنے &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_summary" msgid="2059360676631420073">"اس ایپ کو آپ کے <xliff:g id="PROFILE_NAME">%1$s</xliff:g> کا نظم کرنے کی ضرورت ہے۔ <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"آلہ"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"اجازت دیں"</string>
     <string name="consent_no" msgid="2640796915611404382">"اجازت نہ دیں"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-uz/strings.xml b/packages/CompanionDeviceManager/res/values-uz/strings.xml
index 7221b6d..8505ca9 100644
--- a/packages/CompanionDeviceManager/res/values-uz/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uz/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"qurilma"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"soat"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; qurilmasini boshqarish uchun &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ilovasiga ruxsat bering"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Bu ilova <xliff:g id="PROFILE_NAME">%1$s</xliff:g> profilini boshqarish uchun kerak. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"qurilma"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Ruxsat"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ruxsat berilmasin"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-vi/strings.xml b/packages/CompanionDeviceManager/res/values-vi/strings.xml
index 2819e1d..5dec271 100644
--- a/packages/CompanionDeviceManager/res/values-vi/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-vi/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Companion Device Manager"</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="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"đồng hồ"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Cho phép &lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; quản lý &lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; của bạn"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"Cần có ứng dụng này để quản lý <xliff:g id="PROFILE_NAME">%1$s</xliff:g> của bạn. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"thiết bị"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Cho phép"</string>
     <string name="consent_no" msgid="2640796915611404382">"Không cho phép"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
index 1440c40..f414067 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rCN/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"配套设备管理器"</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="profile_name_generic" msgid="6851028682723034988">"设备"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"手表"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"允许&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_summary" msgid="2059360676631420073">"需要使用此应用,才能管理您的<xliff:g id="PROFILE_NAME">%1$s</xliff:g>。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"设备"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"允许"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允许"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
index e3f1eb1..37f4bd9 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rHK/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"隨附裝置管理工具"</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="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"允許 &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_summary" msgid="2059360676631420073">"必須使用此應用程式,才能管理<xliff:g id="PROFILE_NAME">%1$s</xliff:g>。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
index 9f4041d..76c8379 100644
--- a/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zh-rTW/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"隨附裝置管理員"</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="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"手錶"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"允許「<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_summary" msgid="2059360676631420073">"需使用這個應用程式,才能管理「<xliff:g id="PROFILE_NAME">%1$s</xliff:g>」。<xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"裝置"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"允許"</string>
     <string name="consent_no" msgid="2640796915611404382">"不允許"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values-zu/strings.xml b/packages/CompanionDeviceManager/res/values-zu/strings.xml
index dc933ae..fdfda00 100644
--- a/packages/CompanionDeviceManager/res/values-zu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-zu/strings.xml
@@ -17,11 +17,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4470785958457506021">"Isiphathi sedivayisi esihambisanayo"</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="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string>
-    <string name="profile_name_watch" msgid="576290739483672360">"buka"</string>
     <string name="confirmation_title" msgid="8455544820286920304">"Vumela i-&lt;strong&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/strong&gt; ukuthi iphathe i-&lt;strong&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/strong&gt; yakho"</string>
-    <string name="profile_summary" msgid="2059360676631420073">"I-app iyadingeka ukuphatha i-<xliff:g id="PROFILE_NAME">%1$s</xliff:g> yakho. <xliff:g id="PRIVILEGES_DISCPLAIMER">%2$s</xliff:g>"</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>
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for summary_watch (7113724443198337683) -->
+    <skip />
+    <!-- no translation found for title_app_streaming (4459136600249308574) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (6105916810614498138) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (2996373715966272792) -->
+    <skip />
+    <!-- no translation found for summary_app_streaming (7614171699434639963) -->
+    <skip />
+    <string name="title_automotive_projection" msgid="3296005598978412847"></string>
+    <string name="summary_automotive_projection" msgid="8683801274662496164"></string>
+    <string name="profile_name_generic" msgid="6851028682723034988">"idivayisi"</string>
+    <string name="summary_generic" msgid="2346762210105903720"></string>
     <string name="consent_yes" msgid="8344487259618762872">"Vumela"</string>
     <string name="consent_no" msgid="2640796915611404382">"Ungavumeli"</string>
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/dimens.xml b/packages/CompanionDeviceManager/res/values/dimens.xml
deleted file mode 100644
index da7b0d1..0000000
--- a/packages/CompanionDeviceManager/res/values/dimens.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-
-    <!--  Padding applied on most UI elements  -->
-    <dimen name="padding">12dp</dimen>
-
-</resources>
\ No newline at end of file
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 44748e9..cb8b616 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -19,25 +19,58 @@
     <!-- Title of the CompanionDeviceManager application. [CHAR LIMIT=50] -->
     <string name="app_label">Companion Device Manager</string>
 
-    <!-- Title of the device selection dialog. -->
-    <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>
+    <!-- Title of the device association confirmation dialog. -->
+    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage your &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;</string>
 
-    <!-- The generic placeholder for a device type when nothing specific is known about it [CHAR LIMIT=30] -->
-    <string name="profile_name_generic">device</string>
+    <!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
 
     <!-- The name of the "watch" device type [CHAR LIMIT=30] -->
     <string name="profile_name_watch">watch</string>
 
-    <!-- Title of the device association confirmation dialog. -->
-    <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage your &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;</string>
+    <!-- Title of the device selection dialog. -->
+    <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>
 
-    <!-- Text of the device profile permissions explanation in the association dialog. -->
-    <string name="profile_summary">This app is needed to manage your <xliff:g id="profile_name" example="watch">%1$s</xliff:g>. <xliff:g id="privileges_discplaimer" example="Android Wear will get access to your Notifications, Calendar and Contacts.">%2$s</xliff:g></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" product="default"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.</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" product="tablet"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.</string>
+
+    <!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
+
+    <!-- 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 stream applications?</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_app_streaming" product="default">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this phone when connected.</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_app_streaming" product="tablet">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this tablet when connected.</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of APP_STREAMING profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_app_streaming" product="device">Let &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to provide &lt;strong&gt;<xliff:g id="device_name" example="Pixelbook Go">%2$s</xliff:g>&lt;/strong&gt; remote access to access to applications installed on this device when connected.</string>
+
+    <!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
+
+    <!-- Confirmation for associating an application with a companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
+    <string name="title_automotive_projection"></string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of AUTOMOTIVE_PROJECTION profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_automotive_projection"></string>
+
+    <!-- ================= null profile ================= -->
+
+    <!-- A noun for a companion device with unspecified profile (type) [CHAR LIMIT=30] -->
+    <string name="profile_name_generic">device</string>
+
+    <!-- Description of the privileges the application will get if associated with the companion device of unspecified profile (type) [CHAR LIMIT=NONE] -->
+    <string name="summary_generic"></string>
+
+    <!-- ================= Buttons ================= -->
 
     <!-- Positive button for the device-app association consent dialog [CHAR LIMIT=30] -->
     <string name="consent_yes">Allow</string>
 
     <!-- Negative button for the device-app association consent dialog [CHAR LIMIT=30] -->
     <string name="consent_no">Don\u2019t allow</string>
-
 </resources>
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
deleted file mode 100644
index 9dced47b..0000000
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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.
--->
-
-<resources>
-    <style name="ContainerLayout">
-        <item name="android:orientation">vertical</item>
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:elevation">16dp</item>
-        <item name="android:background">@drawable/dialog_background</item>
-        <item name="android:paddingTop">18dip</item>
-        <item name="android:paddingStart">20dip</item>
-        <item name="android:paddingEnd">16dip</item>
-        <item name="android:paddingBottom">16dip</item>
-        <item name="android:layout_gravity">center</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 a5168cc..cc887c3 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -16,327 +16,356 @@
 
 package com.android.companiondevicemanager;
 
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
-import static android.text.TextUtils.emptyIfNull;
-import static android.text.TextUtils.isEmpty;
-import static android.text.TextUtils.withoutPrefix;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import static com.android.companiondevicemanager.Utils.getApplicationLabel;
+import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
+import static com.android.companiondevicemanager.Utils.prepareResultReceiverForIpc;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
 import android.companion.CompanionDeviceManager;
+import android.companion.IAssociationRequestCallback;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.database.DataSetObserver;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
+import android.net.MacAddress;
 import android.os.Bundle;
-import android.text.Html;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.text.Spanned;
 import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
+import android.widget.Button;
 import android.widget.ListView;
-import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DeviceFilterPair;
-import com.android.internal.util.Preconditions;
-
 public class CompanionDeviceActivity extends Activity {
-
     private static final boolean DEBUG = false;
-    private static final String LOG_TAG = CompanionDeviceActivity.class.getSimpleName();
+    private static final String TAG = CompanionDeviceActivity.class.getSimpleName();
 
-    static CompanionDeviceActivity sInstance;
+    // Keep the following constants in sync with
+    // frameworks/base/services/companion/java/
+    // com/android/server/companion/AssociationRequestsProcessor.java
 
-    View mLoadingIndicator = null;
-    ListView mDeviceListView;
-    private View mPairButton;
-    private View mCancelButton;
+    // AssociationRequestsProcessor <-> UI
+    private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
+    private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
+    private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
 
-    DevicesAdapter mDevicesAdapter;
+    // AssociationRequestsProcessor -> UI
+    private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
+    private static final String EXTRA_ASSOCIATION = "association";
+
+    // UI -> AssociationRequestsProcessor
+    private static final int RESULT_CODE_ASSOCIATION_APPROVED = 0;
+    private static final String EXTRA_MAC_ADDRESS = "mac_address";
+
+    private AssociationRequest mRequest;
+    private IAssociationRequestCallback mAppCallback;
+    private ResultReceiver mCdmServiceReceiver;
+
+    // Always present widgets.
+    private TextView mTitle;
+    private TextView mSummary;
+
+    // Progress indicator is only shown while we are looking for the first suitable device for a
+    // "regular" (ie. not self-managed) association.
+    private View mProgressIndicator;
+
+    // Present for self-managed association requests and "single-device" regular association
+    // regular.
+    private Button mButtonAllow;
+
+    // The list is only shown for multiple-device regular association request, after at least one
+    // matching device is found.
+    private @Nullable ListView mListView;
+    private @Nullable DeviceListAdapter mAdapter;
+
+    // The flag used to prevent double taps, that may lead to sending several requests for creating
+    // an association to CDM.
+    private boolean mAssociationApproved;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        Log.i(LOG_TAG, "Starting UI for " + getService().mRequest);
-
-        if (getService().mDevicesFound.isEmpty()) {
-            Log.e(LOG_TAG, "About to show UI, but no devices to show");
-        }
-
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-        sInstance = this;
-        getService().mActivity = this;
-
-        String deviceProfile = getRequest().getDeviceProfile();
-        String profilePrivacyDisclaimer = emptyIfNull(getRequest()
-                .getDeviceProfilePrivilegesDescription())
-                .replace("APP_NAME", getCallingAppName());
-        boolean useDeviceProfile = deviceProfile != null && !isEmpty(profilePrivacyDisclaimer);
-        String profileName = useDeviceProfile
-                ? getDeviceProfileName(deviceProfile)
-                : getString(R.string.profile_name_generic);
-
-        if (getRequest().isSingleDevice()) {
-            setContentView(R.layout.device_confirmation);
-            final DeviceFilterPair selectedDevice = getService().mDevicesFound.get(0);
-            setTitle(Html.fromHtml(getString(
-                    R.string.confirmation_title,
-                    Html.escapeHtml(getCallingAppName()),
-                    Html.escapeHtml(selectedDevice.getDisplayName())), 0));
-
-            mPairButton = findViewById(R.id.button_pair);
-            mPairButton.setOnClickListener(v -> onDeviceConfirmed(getService().mSelectedDevice));
-            getService().mSelectedDevice = selectedDevice;
-            onSelectionUpdate();
-            if (getRequest().isSkipPrompt()) {
-                onDeviceConfirmed(selectedDevice);
-            }
-        } else {
-            setContentView(R.layout.device_chooser);
-            mPairButton = findViewById(R.id.button_pair);
-            mPairButton.setVisibility(View.GONE);
-            setTitle(Html.fromHtml(getString(R.string.chooser_title,
-                    Html.escapeHtml(profileName),
-                    Html.escapeHtml(getCallingAppName())), 0));
-            mDeviceListView = findViewById(R.id.device_list);
-            mDevicesAdapter = new DevicesAdapter();
-            mDeviceListView.setAdapter(mDevicesAdapter);
-            mDeviceListView.setOnItemClickListener((adapterView, view, pos, l) -> {
-                getService().mSelectedDevice =
-                        (DeviceFilterPair) adapterView.getItemAtPosition(pos);
-                mDevicesAdapter.notifyDataSetChanged();
-            });
-            mDevicesAdapter.registerDataSetObserver(new DataSetObserver() {
-                @Override
-                public void onChanged() {
-                    onSelectionUpdate();
-                }
-            });
-            mDeviceListView.addFooterView(mLoadingIndicator = getProgressBar(), null, false);
-        }
-
-        TextView profileSummary = findViewById(R.id.profile_summary);
-
-        if (useDeviceProfile) {
-            profileSummary.setVisibility(View.VISIBLE);
-            String deviceRef = getRequest().isSingleDevice()
-                    ? getService().mDevicesFound.get(0).getDisplayName()
-                    : profileName;
-            profileSummary.setText(getString(R.string.profile_summary,
-                    deviceRef,
-                    profilePrivacyDisclaimer));
-        } else {
-            profileSummary.setVisibility(View.GONE);
-        }
-
-        mCancelButton = findViewById(R.id.button_cancel);
-        mCancelButton.setOnClickListener(v -> cancel());
     }
 
-    static void notifyDevicesChanged() {
-        if (sInstance != null && sInstance.mDevicesAdapter != null && !sInstance.isFinishing()) {
-            sInstance.mDevicesAdapter.notifyDataSetChanged();
-        }
-    }
+    @Override
+    protected void onStart() {
+        super.onStart();
+        if (DEBUG) Log.d(TAG, "onStart()");
 
-    private AssociationRequest getRequest() {
-        return getService().mRequest;
-    }
+        final Intent intent = getIntent();
+        mRequest = intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+        mAppCallback = IAssociationRequestCallback.Stub.asInterface(
+                intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
+        mCdmServiceReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
 
-    private String getDeviceProfileName(@Nullable String deviceProfile) {
-        if (deviceProfile == null) {
-            return getString(R.string.profile_name_generic);
-        }
-        switch (deviceProfile) {
-            case AssociationRequest.DEVICE_PROFILE_WATCH: {
-                return getString(R.string.profile_name_watch);
-            }
-            default: {
-                Log.w(LOG_TAG,
-                        "No localized profile name found for device profile: " + deviceProfile);
-                return withoutPrefix("android.app.role.COMPANION_DEVICE_", deviceProfile)
-                        .toLowerCase()
-                        .replace('_', ' ');
-            }
-        }
-    }
+        requireNonNull(mRequest);
+        requireNonNull(mAppCallback);
+        requireNonNull(mCdmServiceReceiver);
 
-    private void cancel() {
-        Log.i(LOG_TAG, "cancel()");
-        getService().onCancel();
-        setResult(RESULT_CANCELED);
-        finish();
+        // Start discovery services if needed.
+        if (!mRequest.isSelfManaged()) {
+            CompanionDeviceDiscoveryService.startForRequest(this, mRequest);
+        }
+        // Init UI.
+        initUI();
     }
 
     @Override
     protected void onStop() {
         super.onStop();
-        if (!isFinishing() && !isChangingConfigurations()) {
-            Log.i(LOG_TAG, "onStop() - cancelling");
-            cancel();
+        if (DEBUG) Log.d(TAG, "onStop(), finishing=" + isFinishing());
+
+        // TODO: handle config changes without cancelling.
+        if (!isFinishing()) {
+            cancel(); // will finish()
         }
+
+        // mAdapter may be observing - need to remove it.
+        CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.deleteObservers();
     }
 
     @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        getService().mActivity = null;
-        if (sInstance == this) {
-            sInstance = null;
-        }
-    }
+    protected void onNewIntent(Intent intent) {
+        // Handle another incoming request (while we are not done with the original - mRequest -
+        // yet).
+        final AssociationRequest request = requireNonNull(
+                intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST));
+        if (DEBUG) Log.d(TAG, "onNewIntent(), request=" + request);
 
-    private CharSequence getCallingAppName() {
+        // We can only "process" one request at a time.
+        final IAssociationRequestCallback appCallback = IAssociationRequestCallback.Stub
+                .asInterface(intent.getExtras().getBinder(EXTRA_APPLICATION_CALLBACK));
         try {
-            final PackageManager packageManager = getPackageManager();
-            String callingPackage = Preconditions.checkStringNotEmpty(
-                    getCallingPackage(),
-                    "This activity must be called for result");
-            return packageManager.getApplicationLabel(
-                    packageManager.getApplicationInfo(callingPackage, 0));
-        } catch (PackageManager.NameNotFoundException e) {
-            throw new RuntimeException(e);
+            requireNonNull(appCallback).onFailure("Busy.");
+        } catch (RemoteException ignore) {
         }
     }
 
-    @Override
-    public String getCallingPackage() {
-        return requireNonNull(getRequest().getCallingPackage());
-    }
+    private void initUI() {
+        if (DEBUG) Log.d(TAG, "initUI(), request=" + mRequest);
 
-    @Override
-    public void setTitle(CharSequence title) {
-        final TextView titleView = findViewById(R.id.title);
-        final int padding = getPadding(getResources());
-        titleView.setPadding(padding, padding, padding, padding);
-        titleView.setText(title);
-    }
+        setContentView(R.layout.activity_confirmation);
 
-    private ProgressBar getProgressBar() {
-        final ProgressBar progressBar = new ProgressBar(this);
-        progressBar.setForegroundGravity(Gravity.CENTER_HORIZONTAL);
-        final int padding = getPadding(getResources());
-        progressBar.setPadding(padding, padding, padding, padding);
-        return progressBar;
-    }
+        mTitle = findViewById(R.id.title);
+        mSummary = findViewById(R.id.summary);
 
-    static int getPadding(Resources r) {
-        return r.getDimensionPixelSize(R.dimen.padding);
-    }
+        mListView = findViewById(R.id.device_list);
+        mListView.setOnItemClickListener((av, iv, position, id) -> onListItemClick(position));
 
-    private void onSelectionUpdate() {
-        DeviceFilterPair selectedDevice = getService().mSelectedDevice;
-        if (mPairButton.getVisibility() != View.VISIBLE && selectedDevice != null) {
-            onDeviceConfirmed(selectedDevice);
+        mButtonAllow = findViewById(R.id.button_allow);
+        mButtonAllow.setOnClickListener(this::onAllowButtonClick);
+
+        findViewById(R.id.button_cancel).setOnClickListener(v -> cancel());
+
+        final CharSequence appLabel = getApplicationLabel(this, mRequest.getPackageName());
+        if (mRequest.isSelfManaged()) {
+            initUiForSelfManagedAssociation(appLabel);
+        } else if (mRequest.isSingleDevice()) {
+            initUiForSingleDevice(appLabel);
         } else {
-            mPairButton.setEnabled(selectedDevice != null);
+            initUiForMultipleDevices(appLabel);
         }
     }
 
-    private CompanionDeviceDiscoveryService getService() {
-        return CompanionDeviceDiscoveryService.sInstance;
+    private void onAssociationCreated(@NonNull AssociationInfo association) {
+        if (DEBUG) Log.i(TAG, "onAssociationCreated(), association=" + association);
+
+        // Don't need to notify the app, CdmService has already done that. Just finish.
+        setResultAndFinish(association);
     }
 
-    protected void onDeviceConfirmed(DeviceFilterPair selectedDevice) {
-        Log.i(LOG_TAG, "onDeviceConfirmed(selectedDevice = " + selectedDevice + ")");
-        getService().onDeviceSelected(
-                getCallingPackage(), getDeviceMacAddress(selectedDevice.device));
+    private void cancel() {
+        if (DEBUG) Log.i(TAG, "cancel()");
+
+        // Stop discovery service if it was used.
+        if (!mRequest.isSelfManaged()) {
+            CompanionDeviceDiscoveryService.stop(this);
+        }
+
+        // First send callback to the app directly...
+        try {
+            mAppCallback.onFailure("Cancelled.");
+        } catch (RemoteException ignore) {
+        }
+
+        // ... then set result and finish ("sending" onActivityResult()).
+        setResultAndFinish(null);
     }
 
-    void setResultAndFinish() {
-        Log.i(LOG_TAG, "setResultAndFinish(selectedDevice = "
-                + getService().mSelectedDevice.device + ")");
-        setResult(RESULT_OK,
-                new Intent().putExtra(
-                        CompanionDeviceManager.EXTRA_DEVICE, getService().mSelectedDevice.device));
+    private void setResultAndFinish(@Nullable AssociationInfo association) {
+        if (DEBUG) Log.i(TAG, "setResultAndFinish(), association=" + association);
+
+        final Intent data = new Intent();
+        if (association != null) {
+            data.putExtra(CompanionDeviceManager.EXTRA_ASSOCIATION, association);
+            if (!association.isSelfManaged()) {
+                data.putExtra(CompanionDeviceManager.EXTRA_DEVICE,
+                        association.getDeviceMacAddressAsString());
+            }
+        }
+        setResult(association != null ? RESULT_OK : RESULT_CANCELED, data);
+
         finish();
     }
 
-    class DevicesAdapter extends BaseAdapter {
-        private final Drawable mBluetoothIcon = icon(android.R.drawable.stat_sys_data_bluetooth);
-        private final Drawable mWifiIcon = icon(com.android.internal.R.drawable.ic_wifi_signal_3);
+    private void initUiForSelfManagedAssociation(CharSequence appLabel) {
+        if (DEBUG) Log.i(TAG, "initUiFor_SelfManaged_Association()");
 
-        private SparseArray<Integer> mColors = new SparseArray();
+        final CharSequence deviceName = mRequest.getDisplayName(); // "<device>";
+        final String deviceProfile = mRequest.getDeviceProfile(); // DEVICE_PROFILE_APP_STREAMING;
 
-        private Drawable icon(int drawableRes) {
-            Drawable icon = getResources().getDrawable(drawableRes, null);
-            icon.setTint(Color.DKGRAY);
-            return icon;
+        final Spanned title;
+        final Spanned summary;
+        switch (deviceProfile) {
+            case DEVICE_PROFILE_APP_STREAMING:
+                title = getHtmlFromResources(this, R.string.title_app_streaming, appLabel);
+                summary = getHtmlFromResources(
+                        this, R.string.summary_app_streaming, appLabel, deviceName);
+                break;
+
+            case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
+                title = getHtmlFromResources(this, R.string.title_automotive_projection, appLabel);
+                summary = getHtmlFromResources(
+                        this, R.string.summary_automotive_projection, appLabel, deviceName);
+                break;
+
+            default:
+                throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
+        mTitle.setText(title);
+        mSummary.setText(summary);
 
-        @Override
-        public View getView(
-                int position,
-                @Nullable View convertView,
-                @NonNull ViewGroup parent) {
-            TextView view = convertView instanceof TextView
-                    ? (TextView) convertView
-                    : newView();
-            bind(view, getItem(position));
-            return view;
-        }
-
-        private void bind(TextView textView, DeviceFilterPair device) {
-            textView.setText(device.getDisplayName());
-            textView.setBackgroundColor(
-                    device.equals(getService().mSelectedDevice)
-                            ? getColor(android.R.attr.colorControlHighlight)
-                            : Color.TRANSPARENT);
-            textView.setCompoundDrawablesWithIntrinsicBounds(
-                    device.device instanceof android.net.wifi.ScanResult
-                            ? mWifiIcon
-                            : mBluetoothIcon,
-                    null, null, null);
-            textView.getCompoundDrawables()[0].setTint(getColor(android.R.attr.colorForeground));
-        }
-
-        private TextView newView() {
-            final TextView textView = new TextView(CompanionDeviceActivity.this);
-            textView.setTextColor(getColor(android.R.attr.colorForeground));
-            final int padding = CompanionDeviceActivity.getPadding(getResources());
-            textView.setPadding(padding, padding, padding, padding);
-            textView.setCompoundDrawablePadding(padding);
-            return textView;
-        }
-
-        private int getColor(int colorAttr) {
-            if (mColors.contains(colorAttr)) {
-                return mColors.get(colorAttr);
-            }
-            TypedValue typedValue = new TypedValue();
-            TypedArray a = obtainStyledAttributes(typedValue.data, new int[] { colorAttr });
-            int result = a.getColor(0, 0);
-            a.recycle();
-            mColors.put(colorAttr, result);
-            return result;
-        }
-
-        @Override
-        public int getCount() {
-            return getService().mDevicesFound.size();
-        }
-
-        @Override
-        public DeviceFilterPair getItem(int position) {
-            return getService().mDevicesFound.get(position);
-        }
-
-        @Override
-        public long getItemId(int position) {
-            return position;
-        }
+        mListView.setVisibility(View.GONE);
     }
+
+    private void initUiForSingleDevice(CharSequence appLabel) {
+        if (DEBUG) Log.i(TAG, "initUiFor_SingleDevice()");
+
+        // TODO: use real name
+        final String deviceName = "<device>";
+        final String deviceProfile = mRequest.getDeviceProfile();
+
+        final Spanned title = getHtmlFromResources(
+                this, R.string.confirmation_title, appLabel, deviceName);
+        final Spanned summary;
+        if (deviceProfile == null) {
+            summary = getHtmlFromResources(this, R.string.summary_generic);
+        } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
+            summary = getHtmlFromResources(this, R.string.summary_watch, appLabel, deviceName);
+        } else {
+            throw new RuntimeException("Unsupported profile " + deviceProfile);
+        }
+
+        mTitle.setText(title);
+        mSummary.setText(summary);
+
+        mListView.setVisibility(View.GONE);
+    }
+
+    private void initUiForMultipleDevices(CharSequence appLabel) {
+        if (DEBUG) Log.i(TAG, "initUiFor_MultipleDevices()");
+
+        final String deviceProfile = mRequest.getDeviceProfile();
+
+        final String profileName;
+        final Spanned summary;
+        if (deviceProfile == null) {
+            profileName = getString(R.string.profile_name_generic);
+            summary = getHtmlFromResources(this, R.string.summary_generic);
+        } else if (deviceProfile.equals(DEVICE_PROFILE_WATCH)) {
+            profileName = getString(R.string.profile_name_watch);
+            summary = getHtmlFromResources(this, R.string.summary_watch, appLabel);
+        } else {
+            throw new RuntimeException("Unsupported profile " + deviceProfile);
+        }
+        final Spanned title = getHtmlFromResources(
+                this, R.string.chooser_title, profileName, appLabel);
+
+        mTitle.setText(title);
+        mSummary.setText(summary);
+
+        mAdapter = new DeviceListAdapter(this);
+        CompanionDeviceDiscoveryService.SCAN_RESULTS_OBSERVABLE.addObserver(mAdapter);
+        // TODO: hide the list and show a spinner until a first device matching device is found.
+        mListView.setAdapter(mAdapter);
+
+        // "Remove" consent button: users would need to click on the list item.
+        mButtonAllow.setVisibility(View.GONE);
+    }
+
+    private void onListItemClick(int position) {
+        if (DEBUG) Log.d(TAG, "onListItemClick() " + position);
+
+        final DeviceFilterPair<?> selectedDevice = mAdapter.getItem(position);
+        final MacAddress macAddress = selectedDevice.getMacAddress();
+        onAssociationApproved(macAddress);
+    }
+
+    private void onAllowButtonClick(View v) {
+        if (DEBUG) Log.d(TAG, "onAllowButtonClick()");
+
+        // Disable the button, to prevent more clicks.
+        v.setEnabled(false);
+
+        final MacAddress macAddress;
+        if (mRequest.isSelfManaged()) {
+            macAddress = null;
+        } else {
+            // TODO: implement.
+            throw new UnsupportedOperationException(
+                    "isSingleDevice() requests are not supported yet.");
+        }
+        onAssociationApproved(macAddress);
+    }
+
+    private void onAssociationApproved(@Nullable MacAddress macAddress) {
+        if (mAssociationApproved) return;
+        mAssociationApproved = true;
+
+        if (DEBUG) Log.i(TAG, "onAssociationApproved() macAddress=" + macAddress);
+
+        if (!mRequest.isSelfManaged()) {
+            requireNonNull(macAddress);
+            CompanionDeviceDiscoveryService.stop(this);
+        }
+
+        final Bundle data = new Bundle();
+        data.putParcelable(EXTRA_ASSOCIATION_REQUEST, mRequest);
+        data.putBinder(EXTRA_APPLICATION_CALLBACK, mAppCallback.asBinder());
+        if (macAddress != null) {
+            data.putParcelable(EXTRA_MAC_ADDRESS, macAddress);
+        }
+
+        data.putParcelable(EXTRA_RESULT_RECEIVER,
+                prepareResultReceiverForIpc(mOnAssociationCreatedReceiver));
+
+        mCdmServiceReceiver.send(RESULT_CODE_ASSOCIATION_APPROVED, data);
+    }
+
+    private final ResultReceiver mOnAssociationCreatedReceiver =
+            new ResultReceiver(Handler.getMain()) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle data) {
+                    if (resultCode != RESULT_CODE_ASSOCIATION_CREATED) {
+                        throw new RuntimeException("Unknown result code: " + resultCode);
+                    }
+
+                    final AssociationInfo association = data.getParcelable(EXTRA_ASSOCIATION);
+                    requireNonNull(association);
+
+                    onAssociationCreated(association);
+                }
+            };
 }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
index 126b823..a4ff1dc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java
@@ -16,18 +16,17 @@
 
 package com.android.companiondevicemanager;
 
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
-import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
-
+import static com.android.companiondevicemanager.Utils.runOnMainThread;
 import static com.android.internal.util.ArrayUtils.isEmpty;
-import static com.android.internal.util.CollectionUtils.emptyIfNull;
-import static com.android.internal.util.CollectionUtils.size;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.internal.util.CollectionUtils.filter;
+import static com.android.internal.util.CollectionUtils.find;
+import static com.android.internal.util.CollectionUtils.map;
+
+import static java.util.Objects.requireNonNull;
 
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.PendingIntent;
 import android.app.Service;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
@@ -42,8 +41,6 @@
 import android.companion.BluetoothDeviceFilter;
 import android.companion.BluetoothLeDeviceFilter;
 import android.companion.DeviceFilter;
-import android.companion.IAssociationRequestCallback;
-import android.companion.ICompanionDeviceDiscoveryService;
 import android.companion.WifiDeviceFilter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -53,417 +50,411 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Parcelable;
-import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.Preconditions;
-
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Observable;
 
 public class CompanionDeviceDiscoveryService extends Service {
-
     private static final boolean DEBUG = false;
-    private static final String LOG_TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
+    private static final String TAG = CompanionDeviceDiscoveryService.class.getSimpleName();
 
-    private static final long SCAN_TIMEOUT = 20000;
+    private static final String ACTION_START_DISCOVERY =
+            "com.android.companiondevicemanager.action.START_DISCOVERY";
+    private static final String ACTION_STOP_DISCOVERY =
+            "com.android.companiondevicemanager.action.ACTION_STOP_DISCOVERY";
+    private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
 
-    static CompanionDeviceDiscoveryService sInstance;
+    private static final long SCAN_TIMEOUT = 20_000L; // 20 seconds
 
-    private BluetoothManager mBluetoothManager;
-    private BluetoothAdapter mBluetoothAdapter;
+    // TODO: replace with LiveData-s?
+    static final Observable TIMEOUT_OBSERVABLE = new MyObservable();
+    static final Observable SCAN_RESULTS_OBSERVABLE = new MyObservable();
+
+    private static CompanionDeviceDiscoveryService sInstance;
+
+    private BluetoothManager mBtManager;
+    private BluetoothAdapter mBtAdapter;
     private WifiManager mWifiManager;
-    @Nullable private BluetoothLeScanner mBLEScanner;
-    private ScanSettings mDefaultScanSettings = new ScanSettings.Builder()
-            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
-            .build();
+    private BluetoothLeScanner mBleScanner;
 
-    private List<DeviceFilter<?>> mFilters;
-    private List<BluetoothLeDeviceFilter> mBLEFilters;
-    private List<BluetoothDeviceFilter> mBluetoothFilters;
-    private List<WifiDeviceFilter> mWifiFilters;
-    private List<ScanFilter> mBLEScanFilters;
+    private ScanCallback mBleScanCallback;
+    private BluetoothBroadcastReceiver mBtReceiver;
+    private WifiBroadcastReceiver mWifiReceiver;
 
-    AssociationRequest mRequest;
-    List<DeviceFilterPair> mDevicesFound;
-    DeviceFilterPair mSelectedDevice;
-    IAssociationRequestCallback mApplicationCallback;
+    private boolean mDiscoveryStarted = false;
+    private boolean mDiscoveryStopped = false;
+    private final List<DeviceFilterPair<?>> mDevicesFound = new ArrayList<>();
 
-    AndroidFuture<String> mServiceCallback;
-    boolean mIsScanning = false;
-    @Nullable
-    CompanionDeviceActivity mActivity = null;
+    private final Runnable mTimeoutRunnable = this::timeout;
 
-    private final ICompanionDeviceDiscoveryService mBinder =
-            new ICompanionDeviceDiscoveryService.Stub() {
-        @Override
-        public void startDiscovery(AssociationRequest request,
-                String callingPackage,
-                IAssociationRequestCallback appCallback,
-                AndroidFuture<String> serviceCallback) {
-            Log.i(LOG_TAG,
-                    "startDiscovery() called with: filter = [" + request
-                            + "], appCallback = [" + appCallback + "]"
-                            + "], serviceCallback = [" + serviceCallback + "]");
-            mApplicationCallback = appCallback;
-            mServiceCallback = serviceCallback;
-            Handler.getMain().sendMessage(obtainMessage(
-                    CompanionDeviceDiscoveryService::startDiscovery,
-                    CompanionDeviceDiscoveryService.this, request));
-        }
+    static void startForRequest(
+            @NonNull Context context, @NonNull AssociationRequest associationRequest) {
+        requireNonNull(associationRequest);
+        final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
+        intent.setAction(ACTION_START_DISCOVERY);
+        intent.putExtra(EXTRA_ASSOCIATION_REQUEST, associationRequest);
+        context.startService(intent);
+    }
 
-        @Override
-        public void onAssociationCreated() {
-            Handler.getMain().post(CompanionDeviceDiscoveryService.this::onAssociationCreated);
-        }
-    };
+    static void stop(@NonNull Context context) {
+        final Intent intent = new Intent(context, CompanionDeviceDiscoveryService.class);
+        intent.setAction(ACTION_STOP_DISCOVERY);
+        context.startService(intent);
+    }
 
-    private ScanCallback mBLEScanCallback;
-    private BluetoothBroadcastReceiver mBluetoothBroadcastReceiver;
-    private WifiBroadcastReceiver mWifiBroadcastReceiver;
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        Log.i(LOG_TAG, "onBind(" + intent + ")");
-        return mBinder.asBinder();
+    @MainThread
+    static @NonNull List<DeviceFilterPair<?>> getScanResults() {
+        return sInstance != null ? new ArrayList<>(sInstance.mDevicesFound)
+                : Collections.emptyList();
     }
 
     @Override
     public void onCreate() {
         super.onCreate();
-
-        Log.i(LOG_TAG, "onCreate()");
-
-        mBluetoothManager = getSystemService(BluetoothManager.class);
-        mBluetoothAdapter = mBluetoothManager.getAdapter();
-        mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
-        mWifiManager = getSystemService(WifiManager.class);
-
-        mDevicesFound = new ArrayList<>();
+        if (DEBUG) Log.d(TAG, "onCreate()");
 
         sInstance = this;
-    }
 
-    @MainThread
-    private void startDiscovery(AssociationRequest request) {
-        if (!request.equals(mRequest)) {
-            mRequest = request;
-
-            mFilters = request.getDeviceFilters();
-            mWifiFilters = CollectionUtils.filter(mFilters, WifiDeviceFilter.class);
-            mBluetoothFilters = CollectionUtils.filter(mFilters, BluetoothDeviceFilter.class);
-            mBLEFilters = CollectionUtils.filter(mFilters, BluetoothLeDeviceFilter.class);
-            mBLEScanFilters
-                    = CollectionUtils.map(mBLEFilters, BluetoothLeDeviceFilter::getScanFilter);
-
-            reset();
-        } else {
-            Log.i(LOG_TAG, "startDiscovery: duplicate request: " + request);
-        }
-
-        if (!ArrayUtils.isEmpty(mDevicesFound)) {
-            onReadyToShowUI();
-        }
-
-        // If filtering to get single device by mac address, also search in the set of already
-        // bonded devices to allow linking those directly
-        String singleMacAddressFilter = null;
-        if (mRequest.isSingleDevice()) {
-            int numFilters = size(mBluetoothFilters);
-            for (int i = 0; i < numFilters; i++) {
-                BluetoothDeviceFilter filter = mBluetoothFilters.get(i);
-                if (!TextUtils.isEmpty(filter.getAddress())) {
-                    singleMacAddressFilter = filter.getAddress();
-                    break;
-                }
-            }
-        }
-        if (singleMacAddressFilter != null) {
-            for (BluetoothDevice dev : emptyIfNull(mBluetoothAdapter.getBondedDevices())) {
-                onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
-            }
-            for (BluetoothDevice dev : emptyIfNull(
-                    mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT))) {
-                onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
-            }
-            for (BluetoothDevice dev : emptyIfNull(
-                    mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER))) {
-                onDeviceFound(DeviceFilterPair.findMatch(dev, mBluetoothFilters));
-            }
-        }
-
-        if (shouldScan(mBluetoothFilters)) {
-            final IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
-
-            Log.i(LOG_TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
-            mBluetoothBroadcastReceiver = new BluetoothBroadcastReceiver();
-            registerReceiver(mBluetoothBroadcastReceiver, intentFilter);
-            mBluetoothAdapter.startDiscovery();
-        }
-
-        if (shouldScan(mBLEFilters) && mBLEScanner != null) {
-            Log.i(LOG_TAG, "BLEScanner.startScan");
-            mBLEScanCallback = new BLEScanCallback();
-            mBLEScanner.startScan(mBLEScanFilters, mDefaultScanSettings, mBLEScanCallback);
-        }
-
-        if (shouldScan(mWifiFilters)) {
-            Log.i(LOG_TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
-            mWifiBroadcastReceiver = new WifiBroadcastReceiver();
-            registerReceiver(mWifiBroadcastReceiver,
-                    new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
-            mWifiManager.startScan();
-        }
-        mIsScanning = true;
-        Handler.getMain().sendMessageDelayed(
-                obtainMessage(CompanionDeviceDiscoveryService::stopScan, this),
-                SCAN_TIMEOUT);
-    }
-
-    @MainThread
-    private void onAssociationCreated() {
-        mActivity.setResultAndFinish();
-    }
-
-    private boolean shouldScan(List<? extends DeviceFilter> mediumSpecificFilters) {
-        return !isEmpty(mediumSpecificFilters) || isEmpty(mFilters);
-    }
-
-    @MainThread
-    private void reset() {
-        Log.i(LOG_TAG, "reset()");
-        stopScan();
-        mDevicesFound.clear();
-        mSelectedDevice = null;
-        CompanionDeviceActivity.notifyDevicesChanged();
+        mBtManager = getSystemService(BluetoothManager.class);
+        mBtAdapter = mBtManager.getAdapter();
+        mBleScanner = mBtAdapter.getBluetoothLeScanner();
+        mWifiManager = getSystemService(WifiManager.class);
     }
 
     @Override
-    public boolean onUnbind(Intent intent) {
-        Log.i(LOG_TAG, "onUnbind(intent = " + intent + ")");
-        stopScan();
-        return super.onUnbind(intent);
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        final String action = intent.getAction();
+        if (DEBUG) Log.d(TAG, "onStartCommand() action=" + action);
+
+        switch (action) {
+            case ACTION_START_DISCOVERY:
+                final AssociationRequest request =
+                        intent.getParcelableExtra(EXTRA_ASSOCIATION_REQUEST);
+                startDiscovery(request);
+                break;
+
+            case ACTION_STOP_DISCOVERY:
+                stopDiscoveryAndFinish();
+                break;
+        }
+        return START_NOT_STICKY;
     }
 
-    private void stopScan() {
-        Log.i(LOG_TAG, "stopScan()");
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (DEBUG) Log.d(TAG, "onDestroy()");
 
-        if (!mIsScanning) return;
-        mIsScanning = false;
-
-        if (mActivity != null && mActivity.mDeviceListView != null) {
-            mActivity.mDeviceListView.removeFooterView(mActivity.mLoadingIndicator);
-        }
-
-        mBluetoothAdapter.cancelDiscovery();
-        if (mBluetoothBroadcastReceiver != null) {
-            unregisterReceiver(mBluetoothBroadcastReceiver);
-            mBluetoothBroadcastReceiver = null;
-        }
-        if (mBLEScanner != null) mBLEScanner.stopScan(mBLEScanCallback);
-        if (mWifiBroadcastReceiver != null) {
-            unregisterReceiver(mWifiBroadcastReceiver);
-            mWifiBroadcastReceiver = null;
-        }
-    }
-
-    private void onDeviceFound(@Nullable DeviceFilterPair device) {
-        if (device == null) return;
-
-        Handler.getMain().sendMessage(obtainMessage(
-                CompanionDeviceDiscoveryService::onDeviceFoundMainThread, this, device));
+        sInstance = null;
     }
 
     @MainThread
-    void onDeviceFoundMainThread(@NonNull DeviceFilterPair device) {
-        if (mDevicesFound.contains(device)) {
-            Log.i(LOG_TAG, "Skipping device " + device + " - already among found devices");
-            return;
-        }
+    private void startDiscovery(@NonNull AssociationRequest request) {
+        if (DEBUG) Log.i(TAG, "startDiscovery() request=" + request);
+        requireNonNull(request);
 
-        Log.i(LOG_TAG, "Found device " + device);
+        if (mDiscoveryStarted) throw new RuntimeException("Discovery in progress.");
+        mDiscoveryStarted = true;
 
-        if (mDevicesFound.isEmpty()) {
-            onReadyToShowUI();
-        }
-        mDevicesFound.add(device);
-        CompanionDeviceActivity.notifyDevicesChanged();
-    }
+        final List<DeviceFilter<?>> allFilters = request.getDeviceFilters();
+        final List<BluetoothDeviceFilter> btFilters =
+                filter(allFilters, BluetoothDeviceFilter.class);
+        final List<BluetoothLeDeviceFilter> bleFilters =
+                filter(allFilters, BluetoothLeDeviceFilter.class);
+        final List<WifiDeviceFilter> wifiFilters = filter(allFilters, WifiDeviceFilter.class);
 
-    //TODO also, on timeout -> call onFailure
-    private void onReadyToShowUI() {
-        try {
-            mApplicationCallback.onAssociationPending(PendingIntent.getActivity(
-                    this, 0,
-                    new Intent(this, CompanionDeviceActivity.class),
-                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT
-                            | PendingIntent.FLAG_IMMUTABLE));
-        } catch (RemoteException e) {
-            throw new RuntimeException(e);
-        }
-    }
+        checkBoundDevicesIfNeeded(request, btFilters);
 
-    private void onDeviceLost(@Nullable DeviceFilterPair device) {
-        Log.i(LOG_TAG, "Lost device " + device.getDisplayName());
-        Handler.getMain().sendMessage(obtainMessage(
-                CompanionDeviceDiscoveryService::onDeviceLostMainThread, this, device));
+        // If no filters are specified: look for everything.
+        final boolean forceStartScanningAll = isEmpty(allFilters);
+        // Start BT scanning (if needed)
+        mBtReceiver = startBtScanningIfNeeded(btFilters, forceStartScanningAll);
+        // Start Wi-Fi scanning (if needed)
+        mWifiReceiver = startWifiScanningIfNeeded(wifiFilters, forceStartScanningAll);
+        // Start BLE scanning (if needed)
+        mBleScanCallback = startBleScanningIfNeeded(bleFilters, forceStartScanningAll);
+
+        // Schedule a time-out.
+        Handler.getMain().postDelayed(mTimeoutRunnable, SCAN_TIMEOUT);
     }
 
     @MainThread
-    void onDeviceLostMainThread(@Nullable DeviceFilterPair device) {
-        mDevicesFound.remove(device);
-        CompanionDeviceActivity.notifyDevicesChanged();
-    }
+    private void stopDiscoveryAndFinish() {
+        if (DEBUG) Log.i(TAG, "stopDiscovery()");
 
-    void onDeviceSelected(String callingPackage, String deviceAddress) {
-        if (callingPackage == null || deviceAddress == null) {
+        if (!mDiscoveryStarted) {
+            stopSelf();
             return;
         }
-        mServiceCallback.complete(deviceAddress);
-    }
 
-    void onCancel() {
-        if (DEBUG) Log.i(LOG_TAG, "onCancel()");
-        mActivity = null;
-        mServiceCallback.cancel(true);
-    }
+        if (mDiscoveryStopped) return;
+        mDiscoveryStopped = true;
 
-    /**
-     * A pair of device and a filter that matched this device if any.
-     *
-     * @param <T> device type
-     */
-    static class DeviceFilterPair<T extends Parcelable> {
-        public final T device;
-        @Nullable
-        public final DeviceFilter<T> filter;
-
-        private DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
-            this.device = device;
-            this.filter = filter;
+        // Stop BT discovery.
+        if (mBtReceiver != null) {
+            // Cancel discovery.
+            mBtAdapter.cancelDiscovery();
+            // Unregister receiver.
+            unregisterReceiver(mBtReceiver);
+            mBtReceiver = null;
         }
 
-        /**
-         * {@code (device, null)} if the filters list is empty or null
-         * {@code null} if none of the provided filters match the device
-         * {@code (device, filter)} where filter is among the list of filters and matches the device
-         */
-        @Nullable
-        public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
-                T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
-            if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
-            final DeviceFilter<T> matchingFilter
-                    = CollectionUtils.find(filters, f -> f.matches(dev));
-
-            DeviceFilterPair<T> result = matchingFilter != null
-                    ? new DeviceFilterPair<>(dev, matchingFilter)
-                    : null;
-            if (DEBUG) Log.i(LOG_TAG, "findMatch(dev = " + dev + ", filters = " + filters +
-                    ") -> " + result);
-            return result;
+        // Stop Wi-Fi scanning.
+        if (mWifiReceiver != null) {
+            // TODO: need to stop scan?
+            // Unregister receiver.
+            unregisterReceiver(mWifiReceiver);
+            mWifiReceiver = null;
         }
 
-        public String getDisplayName() {
-            if (filter == null) {
-                Preconditions.checkNotNull(device);
-                if (device instanceof BluetoothDevice) {
-                    return getDeviceDisplayNameInternal((BluetoothDevice) device);
-                } else if (device instanceof android.net.wifi.ScanResult) {
-                    return getDeviceDisplayNameInternal((android.net.wifi.ScanResult) device);
-                } else if (device instanceof ScanResult) {
-                    return getDeviceDisplayNameInternal(((ScanResult) device).getDevice());
-                } else {
-                    throw new IllegalArgumentException("Unknown device type: " + device.getClass());
-                }
+        // Stop BLE scanning.
+        if (mBleScanCallback != null) {
+            mBleScanner.stopScan(mBleScanCallback);
+        }
+
+        Handler.getMain().removeCallbacks(mTimeoutRunnable);
+
+        // "Finish".
+        stopSelf();
+    }
+
+    private void checkBoundDevicesIfNeeded(@NonNull AssociationRequest request,
+            @NonNull List<BluetoothDeviceFilter> btFilters) {
+        // If filtering to get single device by mac address, also search in the set of already
+        // bonded devices to allow linking those directly
+        if (btFilters.isEmpty() || !request.isSingleDevice()) return;
+
+        final BluetoothDeviceFilter singleMacAddressFilter =
+                find(btFilters, filter -> !TextUtils.isEmpty(filter.getAddress()));
+
+        if (singleMacAddressFilter == null) return;
+
+        findAndReportMatches(mBtAdapter.getBondedDevices(), btFilters);
+        findAndReportMatches(mBtManager.getConnectedDevices(BluetoothProfile.GATT), btFilters);
+        findAndReportMatches(
+                mBtManager.getConnectedDevices(BluetoothProfile.GATT_SERVER), btFilters);
+    }
+
+    private void findAndReportMatches(@Nullable Collection<BluetoothDevice> devices,
+            @NonNull List<BluetoothDeviceFilter> filters) {
+        if (devices == null) return;
+
+        for (BluetoothDevice device : devices) {
+            final DeviceFilterPair<BluetoothDevice> match = findMatch(device, filters);
+            if (match != null) {
+                onDeviceFound(match);
             }
-            return filter.getDeviceDisplayName(device);
+        }
+    }
+
+    private BluetoothBroadcastReceiver startBtScanningIfNeeded(
+            List<BluetoothDeviceFilter> filters, boolean force) {
+        if (isEmpty(filters) && !force) return null;
+        if (DEBUG) Log.d(TAG, "registerReceiver(BluetoothDevice.ACTION_FOUND)");
+
+        final BluetoothBroadcastReceiver receiver = new BluetoothBroadcastReceiver(filters);
+
+        final IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
+        registerReceiver(receiver, intentFilter);
+
+        mBtAdapter.startDiscovery();
+
+        return receiver;
+    }
+
+    private WifiBroadcastReceiver startWifiScanningIfNeeded(
+            List<WifiDeviceFilter> filters, boolean force) {
+        if (isEmpty(filters) && !force) return null;
+        if (DEBUG) Log.d(TAG, "registerReceiver(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)");
+
+        final WifiBroadcastReceiver receiver = new WifiBroadcastReceiver(filters);
+
+        final IntentFilter intentFilter = new IntentFilter(
+                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        registerReceiver(receiver, intentFilter);
+
+        mWifiManager.startScan();
+
+        return receiver;
+    }
+
+    private ScanCallback startBleScanningIfNeeded(
+            List<BluetoothLeDeviceFilter> filters, boolean force) {
+        if (isEmpty(filters) && !force) return null;
+        if (DEBUG) Log.d(TAG, "BLEScanner.startScan");
+
+        if (mBleScanner == null) {
+            Log.w(TAG, "BLE Scanner is not available.");
+            return null;
         }
 
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
-            return Objects.equals(getDeviceMacAddress(device), getDeviceMacAddress(that.device));
-        }
+        final BLEScanCallback callback = new BLEScanCallback(filters);
 
-        @Override
-        public int hashCode() {
-            return Objects.hash(getDeviceMacAddress(device));
-        }
+        final List<ScanFilter> scanFilters = map(
+                filters, BluetoothLeDeviceFilter::getScanFilter);
+        final ScanSettings scanSettings = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+                .build();
+        mBleScanner.startScan(scanFilters, scanSettings, callback);
 
-        @Override
-        public String toString() {
-            return "DeviceFilterPair{"
-                    + "device=" + device + " " + getDisplayName()
-                    + ", filter=" + filter
-                    + '}';
-        }
+        return callback;
+    }
+
+    private void onDeviceFound(@NonNull DeviceFilterPair<?> device) {
+        runOnMainThread(() -> {
+            if (DEBUG) Log.v(TAG, "onDeviceFound() " + device);
+            if (mDevicesFound.contains(device)) {
+                // TODO: update the device instead of ignoring (new found device may contain
+                //  additional/updated info, eg. name of the device).
+                if (DEBUG) {
+                    Log.d(TAG, "onDeviceFound() " + device.toShortString()
+                            + " - Already seen: ignore.");
+                }
+                return;
+            }
+            if (DEBUG) Log.i(TAG, "onDeviceFound() " + device.toShortString() + " - New device.");
+
+            // First: make change.
+            mDevicesFound.add(device);
+            // Then: notify observers.
+            SCAN_RESULTS_OBSERVABLE.notifyObservers();
+        });
+    }
+
+    private void onDeviceLost(@Nullable DeviceFilterPair<?> device) {
+        runOnMainThread(() -> {
+            if (DEBUG) Log.i(TAG, "onDeviceLost(), device=" + device.toShortString());
+
+            // First: make change.
+            mDevicesFound.remove(device);
+            // Then: notify observers.
+            SCAN_RESULTS_OBSERVABLE.notifyObservers();
+        });
+    }
+
+    private void timeout() {
+        if (DEBUG) Log.i(TAG, "timeout()");
+        stopDiscoveryAndFinish();
+        TIMEOUT_OBSERVABLE.notifyObservers();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
     }
 
     private class BLEScanCallback extends ScanCallback {
+        final List<BluetoothLeDeviceFilter> mFilters;
 
-        public BLEScanCallback() {
-            if (DEBUG) Log.i(LOG_TAG, "new BLEScanCallback() -> " + this);
+        BLEScanCallback(List<BluetoothLeDeviceFilter> filters) {
+            mFilters = filters;
         }
 
         @Override
         public void onScanResult(int callbackType, ScanResult result) {
             if (DEBUG) {
-                Log.i(LOG_TAG,
-                        "BLE.onScanResult(callbackType = " + callbackType + ", result = " + result
-                                + ")");
+                Log.v(TAG, "BLE.onScanResult() callback=" + callbackType + ", result=" + result);
             }
-            final DeviceFilterPair<ScanResult> deviceFilterPair
-                    = DeviceFilterPair.findMatch(result, mBLEFilters);
-            if (deviceFilterPair == null) return;
+
+            final DeviceFilterPair<ScanResult> match = findMatch(result, mFilters);
+            if (match == null) return;
+
             if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
-                onDeviceLost(deviceFilterPair);
+                onDeviceLost(match);
             } else {
-                onDeviceFound(deviceFilterPair);
+                // TODO: check this logic.
+                onDeviceFound(match);
             }
         }
     }
 
     private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+        final List<BluetoothDeviceFilter> mFilters;
+
+        BluetoothBroadcastReceiver(List<BluetoothDeviceFilter> filters) {
+            this.mFilters = filters;
+        }
+
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (DEBUG) {
-                Log.i(LOG_TAG,
-                        "BL.onReceive(context = " + context + ", intent = " + intent + ")");
-            }
+            final String action = intent.getAction();
             final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            final DeviceFilterPair<BluetoothDevice> deviceFilterPair
-                    = DeviceFilterPair.findMatch(device, mBluetoothFilters);
-            if (deviceFilterPair == null) return;
-            if (intent.getAction().equals(BluetoothDevice.ACTION_FOUND)) {
-                onDeviceFound(deviceFilterPair);
+
+            if (DEBUG) Log.v(TAG, action + ", device=" + device);
+
+            if (action == null) return;
+
+            final DeviceFilterPair<BluetoothDevice> match = findMatch(device, mFilters);
+            if (match == null) return;
+
+            if (action.equals(BluetoothDevice.ACTION_FOUND)) {
+                onDeviceFound(match);
             } else {
-                onDeviceLost(deviceFilterPair);
+                // TODO: check this logic.
+                onDeviceLost(match);
             }
         }
     }
 
     private class WifiBroadcastReceiver extends BroadcastReceiver {
+        final List<WifiDeviceFilter> mFilters;
+
+        private WifiBroadcastReceiver(List<WifiDeviceFilter> filters) {
+            this.mFilters = filters;
+        }
+
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
-                List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
+            if (!Objects.equals(intent.getAction(), WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                return;
+            }
 
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Wifi scan results: " + TextUtils.join("\n", scanResults));
-                }
+            final List<android.net.wifi.ScanResult> scanResults = mWifiManager.getScanResults();
+            if (DEBUG) {
+                Log.v(TAG, "WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, results:\n  "
+                        + TextUtils.join("\n  ", scanResults));
+            }
 
-                for (int i = 0; i < scanResults.size(); i++) {
-                    onDeviceFound(DeviceFilterPair.findMatch(scanResults.get(i), mWifiFilters));
+            for (int i = 0; i < scanResults.size(); i++) {
+                final android.net.wifi.ScanResult scanResult = scanResults.get(i);
+                final DeviceFilterPair<?> match = findMatch(scanResult, mFilters);
+                if (match != null) {
+                    onDeviceFound(match);
                 }
             }
         }
     }
+
+    /**
+     * {@code (device, null)} if the filters list is empty or null
+     * {@code null} if none of the provided filters match the device
+     * {@code (device, filter)} where filter is among the list of filters and matches the device
+     */
+    @Nullable
+    public static <T extends Parcelable> DeviceFilterPair<T> findMatch(
+            T dev, @Nullable List<? extends DeviceFilter<T>> filters) {
+        if (isEmpty(filters)) return new DeviceFilterPair<>(dev, null);
+        final DeviceFilter<T> matchingFilter = find(filters, f -> f.matches(dev));
+
+        DeviceFilterPair<T> result = matchingFilter != null
+                ? new DeviceFilterPair<>(dev, matchingFilter) : null;
+        if (DEBUG) {
+            Log.v(TAG, "findMatch(dev=" + dev + ", filters=" + filters + ") -> " + result);
+        }
+        return result;
+    }
+
+    private static class MyObservable extends Observable {
+        @Override
+        public void notifyObservers() {
+            setChanged();
+            super.notifyObservers();
+        }
+    }
 }
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceFilterPair.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceFilterPair.java
new file mode 100644
index 0000000..faca1ae
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceFilterPair.java
@@ -0,0 +1,108 @@
+/*
+ * 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.companiondevicemanager;
+
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceDisplayNameInternal;
+import static android.companion.BluetoothDeviceFilterUtils.getDeviceMacAddress;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.companion.DeviceFilter;
+import android.net.MacAddress;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A pair of device and a filter that matched this device if any.
+ *
+ * @param <T> device type.
+ */
+class DeviceFilterPair<T extends Parcelable> {
+    private final T mDevice;
+    private final @Nullable DeviceFilter<T> mFilter;
+
+    DeviceFilterPair(T device, @Nullable DeviceFilter<T> filter) {
+        this.mDevice = device;
+        this.mFilter = filter;
+    }
+
+    T getDevice() {
+        return mDevice;
+    }
+
+    String getDisplayName() {
+        if (mFilter != null) mFilter.getDeviceDisplayName(mDevice);
+
+        if (mDevice instanceof BluetoothDevice) {
+            return getDeviceDisplayNameInternal((BluetoothDevice) mDevice);
+        } else if (mDevice instanceof android.bluetooth.le.ScanResult) {
+            final android.bluetooth.le.ScanResult bleScanResult =
+                    (android.bluetooth.le.ScanResult) mDevice;
+            return getDeviceDisplayNameInternal(bleScanResult.getDevice());
+        } else if (mDevice instanceof android.net.wifi.ScanResult) {
+            final android.net.wifi.ScanResult wifiScanResult =
+                    (android.net.wifi.ScanResult) mDevice;
+            return getDeviceDisplayNameInternal(wifiScanResult);
+        } else {
+            throw new IllegalArgumentException("Unknown device type: " + mDevice.getClass());
+        }
+    }
+
+    @NonNull MacAddress getMacAddress() {
+        return MacAddress.fromString(getDeviceMacAddress(getDevice()));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DeviceFilterPair<?> that = (DeviceFilterPair<?>) o;
+        return Objects.equals(getDeviceMacAddress(mDevice), getDeviceMacAddress(that.mDevice));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getDeviceMacAddress(mDevice));
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceFilterPair{"
+                + "device=" + mDevice + " " + getDisplayName()
+                + ", filter=" + mFilter
+                + '}';
+    }
+
+    @NonNull String toShortString() {
+        return '(' + getDeviceTypeAsString() + ") " + getMacAddress() + " '" + getDisplayName()
+                + '\'';
+    }
+
+    private @NonNull String getDeviceTypeAsString() {
+        if (mDevice instanceof BluetoothDevice) {
+            return "BT";
+        } else if (mDevice instanceof android.bluetooth.le.ScanResult) {
+            return "BLE";
+        } else if (mDevice instanceof android.net.wifi.ScanResult) {
+            return "Wi-Fi";
+        } else {
+            return "Unknown";
+        }
+    }
+}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
new file mode 100644
index 0000000..cf2a2bf
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceListAdapter.java
@@ -0,0 +1,123 @@
+/*
+ * 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.companiondevicemanager;
+
+import android.annotation.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+
+/**
+ * Adapter for the list of "found" devices.
+ */
+class DeviceListAdapter extends BaseAdapter implements Observer {
+    private final Context mContext;
+    private final Resources mResources;
+
+    private final Drawable mBluetoothIcon;
+    private final Drawable mWifiIcon;
+
+    private final @ColorInt int mTextColor;
+
+    // List if pairs (display name, address)
+    private List<DeviceFilterPair<?>> mDevices;
+
+    DeviceListAdapter(Context context) {
+        mContext = context;
+        mResources = context.getResources();
+        mBluetoothIcon = getTintedIcon(mResources, android.R.drawable.stat_sys_data_bluetooth);
+        mWifiIcon = getTintedIcon(mResources, com.android.internal.R.drawable.ic_wifi_signal_3);
+        mTextColor = getColor(context, android.R.attr.colorForeground);
+    }
+
+    @Override
+    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+        final TextView view = convertView != null ? (TextView) convertView : newView();
+        bind(view, getItem(position));
+        return view;
+    }
+
+    private void bind(TextView textView, DeviceFilterPair<?> item) {
+        textView.setText(item.getDisplayName());
+        textView.setBackgroundColor(Color.TRANSPARENT);
+        /*
+        textView.setCompoundDrawablesWithIntrinsicBounds(
+                item.getDevice() instanceof android.net.wifi.ScanResult
+                        ? mWifiIcon
+                        : mBluetoothIcon,
+                null, null, null);
+        textView.getCompoundDrawables()[0].setTint(mTextColor);
+         */
+    }
+
+    private TextView newView() {
+        final TextView textView = new TextView(mContext);
+        textView.setTextColor(mTextColor);
+        final int padding = 24;
+        textView.setPadding(padding, padding, padding, padding);
+        //textView.setCompoundDrawablePadding(padding);
+        return textView;
+    }
+
+    @Override
+    public int getCount() {
+        return mDevices != null ? mDevices.size() : 0;
+    }
+
+    @Override
+    public DeviceFilterPair<?> getItem(int position) {
+        return mDevices.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public void update(Observable o, Object arg) {
+        mDevices = CompanionDeviceDiscoveryService.getScanResults();
+        notifyDataSetChanged();
+    }
+
+    private @ColorInt int getColor(Context context, int attr) {
+        final TypedArray a = context.obtainStyledAttributes(new TypedValue().data,
+                new int[] { attr });
+        final int color = a.getColor(0, 0);
+        a.recycle();
+        return color;
+    }
+
+    private static Drawable getTintedIcon(Resources resources, int drawableRes) {
+        Drawable icon = resources.getDrawable(drawableRes, null);
+        icon.setTint(Color.DKGRAY);
+        return icon;
+    }
+}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
new file mode 100644
index 0000000..eab421e
--- /dev/null
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/Utils.java
@@ -0,0 +1,84 @@
+/*
+ * 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.companiondevicemanager;
+
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.text.Html;
+import android.text.Spanned;
+
+/**
+ * Utilities.
+ */
+class Utils {
+
+    /**
+     * Convert an instance of a "locally-defined" ResultReceiver to an instance of
+     * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
+     * unmarshall.
+     */
+    static <T extends ResultReceiver> ResultReceiver prepareResultReceiverForIpc(T resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
+    }
+
+    static @NonNull CharSequence getApplicationLabel(
+            @NonNull Context context, @NonNull String packageName) {
+        final PackageManager packageManager = context.getPackageManager();
+        final ApplicationInfo appInfo;
+        try {
+            appInfo = packageManager.getApplicationInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        return packageManager.getApplicationLabel(appInfo);
+    }
+
+    static Spanned getHtmlFromResources(
+            @NonNull Context context, @StringRes int resId, CharSequence... formatArgs) {
+        final String[] escapedArgs = new String[formatArgs.length];
+        for (int i = 0; i < escapedArgs.length; i++) {
+            escapedArgs[i] = Html.escapeHtml(formatArgs[i]);
+        }
+        final String plain = context.getString(resId, (Object[]) escapedArgs);
+        return Html.fromHtml(plain, 0);
+    }
+
+    static void runOnMainThread(Runnable runnable) {
+        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
+            runnable.run();
+        } else {
+            Handler.getMain().post(runnable);
+        }
+    }
+
+    private Utils() {
+    }
+}
diff --git a/packages/ConnectivityT/OWNERS b/packages/ConnectivityT/OWNERS
new file mode 100644
index 0000000..e267d19
--- /dev/null
+++ b/packages/ConnectivityT/OWNERS
@@ -0,0 +1,2 @@
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
new file mode 100644
index 0000000..0bda923
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -0,0 +1,157 @@
+//
+// 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
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// NetworkStats related libraries.
+
+filegroup {
+    name: "framework-connectivity-netstats-internal-sources",
+    srcs: [
+        "src/android/app/usage/*.java",
+        "src/android/net/DataUsage*.*",
+        "src/android/net/INetworkStats*.*",
+        "src/android/net/NetworkIdentity*.java",
+        "src/android/net/NetworkStateSnapshot.*",
+        "src/android/net/NetworkStats*.*",
+        "src/android/net/NetworkTemplate.*",
+        "src/android/net/TrafficStats.java",
+        "src/android/net/UnderlyingNetworkInfo.*",
+        "src/android/net/netstats/**/*.*",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-netstats-aidl-export-sources",
+    srcs: [
+        "aidl-export/android/net/NetworkStats.aidl",
+        "aidl-export/android/net/NetworkTemplate.aidl",
+    ],
+    path: "aidl-export",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-netstats-sources",
+    srcs: [
+        ":framework-connectivity-netstats-internal-sources",
+        ":framework-connectivity-netstats-aidl-export-sources",
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// Nsd related libraries.
+
+filegroup {
+    name: "framework-connectivity-nsd-internal-sources",
+    srcs: [
+        "src/android/net/nsd/*.aidl",
+        "src/android/net/nsd/*.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-nsd-aidl-export-sources",
+    srcs: [
+        "aidl-export/android/net/nsd/*.aidl",
+    ],
+    path: "aidl-export",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-nsd-sources",
+    srcs: [
+        ":framework-connectivity-nsd-internal-sources",
+        ":framework-connectivity-nsd-aidl-export-sources",
+    ],
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// IpSec related libraries.
+
+filegroup {
+    name: "framework-connectivity-ipsec-sources",
+    srcs: [
+        "src/android/net/IIpSecService.aidl",
+        "src/android/net/IpSec*.*",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// Ethernet related libraries.
+
+filegroup {
+    name: "framework-connectivity-ethernet-sources",
+    srcs: [
+        "src/android/net/EthernetManager.java",
+        "src/android/net/EthernetNetworkSpecifier.java",
+        "src/android/net/IEthernetManager.aidl",
+        "src/android/net/IEthernetServiceListener.aidl",
+        "src/android/net/ITetheredInterfaceCallback.aidl",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// Connectivity-T common libraries.
+
+filegroup {
+    name: "framework-connectivity-tiramisu-internal-sources",
+    srcs: [
+        "src/android/net/ConnectivityFrameworkInitializerTiramisu.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+filegroup {
+    name: "framework-connectivity-tiramisu-sources",
+    srcs: [
+        ":framework-connectivity-ethernet-sources",
+        ":framework-connectivity-ipsec-sources",
+        ":framework-connectivity-netstats-sources",
+        ":framework-connectivity-nsd-sources",
+        ":framework-connectivity-tiramisu-internal-sources",
+    ],
+    visibility: ["//frameworks/base"],
+}
diff --git a/core/java/android/net/NetworkStats.aidl b/packages/ConnectivityT/framework-t/aidl-export/android/net/NetworkStats.aidl
similarity index 100%
rename from core/java/android/net/NetworkStats.aidl
rename to packages/ConnectivityT/framework-t/aidl-export/android/net/NetworkStats.aidl
diff --git a/core/java/android/net/NetworkTemplate.aidl b/packages/ConnectivityT/framework-t/aidl-export/android/net/NetworkTemplate.aidl
similarity index 100%
rename from core/java/android/net/NetworkTemplate.aidl
rename to packages/ConnectivityT/framework-t/aidl-export/android/net/NetworkTemplate.aidl
diff --git a/packages/Nsd/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl b/packages/ConnectivityT/framework-t/aidl-export/android/net/nsd/NsdServiceInfo.aidl
similarity index 100%
rename from packages/Nsd/framework/aidl-export/android/net/nsd/NsdServiceInfo.aidl
rename to packages/ConnectivityT/framework-t/aidl-export/android/net/nsd/NsdServiceInfo.aidl
diff --git a/core/java/android/app/usage/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
similarity index 100%
rename from core/java/android/app/usage/NetworkStats.java
rename to packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStats.java
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
similarity index 98%
rename from core/java/android/app/usage/NetworkStatsManager.java
rename to packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
index 8a6c85d..ca83309 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java
@@ -45,8 +45,6 @@
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.DataUnit;
@@ -135,15 +133,6 @@
 
     private int mFlags;
 
-    /**
-     * {@hide}
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public NetworkStatsManager(Context context) throws ServiceNotFoundException {
-        this(context, INetworkStatsService.Stub.asInterface(
-                ServiceManager.getServiceOrThrow(Context.NETWORK_STATS_SERVICE)));
-    }
-
     /** @hide */
     @VisibleForTesting
     public NetworkStatsManager(Context context, INetworkStatsService service) {
diff --git a/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
new file mode 100644
index 0000000..630f902e
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/ConnectivityFrameworkInitializerTiramisu.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
+
+/**
+ * Class for performing registration for Connectivity services which are exposed via updatable APIs
+ * since Android T.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class ConnectivityFrameworkInitializerTiramisu {
+    private ConnectivityFrameworkInitializerTiramisu() {}
+
+    /**
+     * Called by {@link SystemServiceRegistry}'s static initializer and registers nsd services to
+     * {@link Context}, so that {@link Context#getSystemService} can return them.
+     *
+     * @throws IllegalStateException if this is called anywhere besides
+     * {@link SystemServiceRegistry}.
+     */
+    public static void registerServiceWrappers() {
+        SystemServiceRegistry.registerContextAwareService(
+                Context.NSD_SERVICE,
+                NsdManager.class,
+                (context, serviceBinder) -> {
+                    INsdManager service = INsdManager.Stub.asInterface(serviceBinder);
+                    return new NsdManager(context, service);
+                }
+        );
+    }
+}
diff --git a/core/java/android/net/DataUsageRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.aidl
similarity index 100%
rename from core/java/android/net/DataUsageRequest.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.aidl
diff --git a/core/java/android/net/DataUsageRequest.java b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
similarity index 98%
rename from core/java/android/net/DataUsageRequest.java
rename to packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
index b06d515..f0ff465 100644
--- a/core/java/android/net/DataUsageRequest.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/DataUsageRequest.java
@@ -75,7 +75,7 @@
                 @Override
                 public DataUsageRequest createFromParcel(Parcel in) {
                     int requestId = in.readInt();
-                    NetworkTemplate template = in.readParcelable(null);
+                    NetworkTemplate template = in.readParcelable(null, android.net.NetworkTemplate.class);
                     long thresholdInBytes = in.readLong();
                     DataUsageRequest result = new DataUsageRequest(requestId, template,
                             thresholdInBytes);
diff --git a/core/java/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
similarity index 100%
rename from core/java/android/net/EthernetManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
diff --git a/core/java/android/net/EthernetNetworkSpecifier.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
similarity index 100%
rename from core/java/android/net/EthernetNetworkSpecifier.java
rename to packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
diff --git a/core/java/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
similarity index 100%
rename from core/java/android/net/IEthernetManager.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
diff --git a/core/java/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
similarity index 100%
rename from core/java/android/net/IEthernetServiceListener.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
diff --git a/core/java/android/net/IIpSecService.aidl b/packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
similarity index 100%
rename from core/java/android/net/IIpSecService.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IIpSecService.aidl
diff --git a/core/java/android/net/INetworkStatsService.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
similarity index 100%
rename from core/java/android/net/INetworkStatsService.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/INetworkStatsService.aidl
diff --git a/core/java/android/net/INetworkStatsSession.aidl b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
similarity index 79%
rename from core/java/android/net/INetworkStatsSession.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
index f13f2cb..dfedf66 100644
--- a/core/java/android/net/INetworkStatsSession.aidl
+++ b/packages/ConnectivityT/framework-t/src/android/net/INetworkStatsSession.aidl
@@ -33,7 +33,17 @@
     @UnsupportedAppUsage
     NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields);
 
-    /** Return network layer usage summary per UID for traffic that matches template. */
+    /**
+     * Return network layer usage summary per UID for traffic that matches template.
+     *
+     * <p>The resulting {@code NetworkStats#getElapsedRealtime()} contains time delta between
+     * {@code start} and {@code end}.
+     *
+     * @param template - a predicate to filter netstats.
+     * @param start - start of the range, timestamp in milliseconds since the epoch.
+     * @param end - end of the range, timestamp in milliseconds since the epoch.
+     * @param includeTags - includes data usage tags if true.
+     */
     @UnsupportedAppUsage
     NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags);
     /** Return historical network layer stats for specific UID traffic that matches template. */
diff --git a/core/java/android/net/ITetheredInterfaceCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
similarity index 100%
rename from core/java/android/net/ITetheredInterfaceCallback.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
diff --git a/core/java/android/net/IpSecAlgorithm.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
similarity index 97%
rename from core/java/android/net/IpSecAlgorithm.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
index 8605248..a84e7a9 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecAlgorithm.java
@@ -23,7 +23,6 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.HexDump;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -297,7 +296,7 @@
         return mTruncLenBits;
     }
 
-    /* Parcelable Implementation */
+    /** Parcelable Implementation */
     public int describeContents() {
         return 0;
     }
@@ -469,20 +468,12 @@
         }
     }
 
-    // Because encryption keys are sensitive and userdebug builds are used by large user pools
-    // such as beta testers, we only allow sensitive info such as keys on eng builds.
-    private static boolean isUnsafeBuild() {
-        return Build.IS_DEBUGGABLE && Build.IS_ENG;
-    }
-
     @Override
     @NonNull
     public String toString() {
         return new StringBuilder()
                 .append("{mName=")
                 .append(mName)
-                .append(", mKey=")
-                .append(isUnsafeBuild() ? HexDump.toHexString(mKey) : "<hidden>")
                 .append(", mTruncLenBits=")
                 .append(mTruncLenBits)
                 .append("}")
diff --git a/core/java/android/net/IpSecConfig.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
similarity index 100%
rename from core/java/android/net/IpSecConfig.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.aidl
diff --git a/core/java/android/net/IpSecConfig.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
similarity index 97%
rename from core/java/android/net/IpSecConfig.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
index 575c5ed..03bb187 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecConfig.java
@@ -267,14 +267,14 @@
         mMode = in.readInt();
         mSourceAddress = in.readString();
         mDestinationAddress = in.readString();
-        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader());
+        mNetwork = (Network) in.readParcelable(Network.class.getClassLoader(), android.net.Network.class);
         mSpiResourceId = in.readInt();
         mEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
         mAuthentication =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
         mAuthenticatedEncryption =
-                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader());
+                (IpSecAlgorithm) in.readParcelable(IpSecAlgorithm.class.getClassLoader(), android.net.IpSecAlgorithm.class);
         mEncapType = in.readInt();
         mEncapSocketResourceId = in.readInt();
         mEncapRemotePort = in.readInt();
diff --git a/core/java/android/net/IpSecManager.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
similarity index 98%
rename from core/java/android/net/IpSecManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
index c106807..8376299 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecManager.java
@@ -99,9 +99,9 @@
 
     /** @hide */
     public interface Status {
-        public static final int OK = 0;
-        public static final int RESOURCE_UNAVAILABLE = 1;
-        public static final int SPI_UNAVAILABLE = 2;
+        int OK = 0;
+        int RESOURCE_UNAVAILABLE = 1;
+        int SPI_UNAVAILABLE = 2;
     }
 
     /** @hide */
@@ -276,7 +276,7 @@
      * @param destinationAddress the destination address for traffic bearing the requested SPI.
      *     For inbound traffic, the destination should be an address currently assigned on-device.
      * @return the reserved SecurityParameterIndex
-     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     * @throws ResourceUnavailableException indicating that too many SPIs are
      *     currently allocated for this user
      */
     @NonNull
@@ -307,9 +307,9 @@
      * @param requestedSpi the requested SPI. The range 1-255 is reserved and may not be used. See
      *     RFC 4303 Section 2.1.
      * @return the reserved SecurityParameterIndex
-     * @throws {@link #ResourceUnavailableException} indicating that too many SPIs are
+     * @throws ResourceUnavailableException indicating that too many SPIs are
      *     currently allocated for this user
-     * @throws {@link #SpiUnavailableException} indicating that the requested SPI could not be
+     * @throws SpiUnavailableException indicating that the requested SPI could not be
      *     reserved
      */
     @NonNull
diff --git a/core/java/android/net/IpSecSpiResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.aidl
diff --git a/core/java/android/net/IpSecSpiResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
similarity index 100%
rename from core/java/android/net/IpSecSpiResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecSpiResponse.java
diff --git a/core/java/android/net/IpSecTransform.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
similarity index 100%
rename from core/java/android/net/IpSecTransform.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransform.java
diff --git a/core/java/android/net/IpSecTransformResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTransformResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.aidl
diff --git a/core/java/android/net/IpSecTransformResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
similarity index 94%
rename from core/java/android/net/IpSecTransformResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
index a384889..363f316 100644
--- a/core/java/android/net/IpSecTransformResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTransformResponse.java
@@ -60,7 +60,8 @@
         resourceId = in.readInt();
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<IpSecTransformResponse> CREATOR =
+    @android.annotation.NonNull
+    public static final Parcelable.Creator<IpSecTransformResponse> CREATOR =
             new Parcelable.Creator<IpSecTransformResponse>() {
                 public IpSecTransformResponse createFromParcel(Parcel in) {
                     return new IpSecTransformResponse(in);
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.aidl
diff --git a/core/java/android/net/IpSecTunnelInterfaceResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
similarity index 95%
rename from core/java/android/net/IpSecTunnelInterfaceResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
index e3411e0..127e30a 100644
--- a/core/java/android/net/IpSecTunnelInterfaceResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecTunnelInterfaceResponse.java
@@ -65,7 +65,8 @@
         interfaceName = in.readString();
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
+    @android.annotation.NonNull
+    public static final Parcelable.Creator<IpSecTunnelInterfaceResponse> CREATOR =
             new Parcelable.Creator<IpSecTunnelInterfaceResponse>() {
                 public IpSecTunnelInterfaceResponse createFromParcel(Parcel in) {
                     return new IpSecTunnelInterfaceResponse(in);
diff --git a/core/java/android/net/IpSecUdpEncapResponse.aidl b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
similarity index 100%
rename from core/java/android/net/IpSecUdpEncapResponse.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.aidl
diff --git a/core/java/android/net/IpSecUdpEncapResponse.java b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
similarity index 94%
rename from core/java/android/net/IpSecUdpEncapResponse.java
rename to packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
index 4e7ba9b..390af82 100644
--- a/core/java/android/net/IpSecUdpEncapResponse.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/IpSecUdpEncapResponse.java
@@ -18,6 +18,7 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 
@@ -80,10 +81,11 @@
         status = in.readInt();
         resourceId = in.readInt();
         port = in.readInt();
-        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        fileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
+    @android.annotation.NonNull
+    public static final Parcelable.Creator<IpSecUdpEncapResponse> CREATOR =
             new Parcelable.Creator<IpSecUdpEncapResponse>() {
                 public IpSecUdpEncapResponse createFromParcel(Parcel in) {
                     return new IpSecUdpEncapResponse(in);
diff --git a/core/java/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
similarity index 100%
rename from core/java/android/net/NetworkIdentity.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java
diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
similarity index 96%
rename from services/core/java/com/android/server/net/NetworkIdentitySet.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
index 22ed781..abbebef 100644
--- a/services/core/java/com/android/server/net/NetworkIdentitySet.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.net;
+package android.net;
 
-import android.net.NetworkIdentity;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+
 import android.service.NetworkIdentitySetProto;
 import android.util.proto.ProtoOutputStream;
 
@@ -25,8 +26,6 @@
 import java.io.IOException;
 import java.util.HashSet;
 
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-
 /**
  * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
  * active on that interface.
@@ -97,6 +96,9 @@
         }
     }
 
+    /**
+     * Method to serialize this object into a {@code DataOutput}.
+     */
     public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_OEM_MANAGED_NETWORK);
         out.writeInt(size());
@@ -179,6 +181,9 @@
         return ident.compareTo(anotherIdent);
     }
 
+    /**
+     * Method to dump this object into proto debug file.
+     */
     public void dumpDebug(ProtoOutputStream proto, long tag) {
         final long start = proto.start(tag);
 
diff --git a/core/java/android/net/NetworkStateSnapshot.aidl b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
similarity index 100%
rename from core/java/android/net/NetworkStateSnapshot.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.aidl
diff --git a/core/java/android/net/NetworkStateSnapshot.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
similarity index 95%
rename from core/java/android/net/NetworkStateSnapshot.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
index 3915634..d577aa8 100644
--- a/core/java/android/net/NetworkStateSnapshot.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStateSnapshot.java
@@ -73,9 +73,9 @@
 
     /** @hide */
     public NetworkStateSnapshot(@NonNull Parcel in) {
-        mNetwork = in.readParcelable(null);
-        mNetworkCapabilities = in.readParcelable(null);
-        mLinkProperties = in.readParcelable(null);
+        mNetwork = in.readParcelable(null, android.net.Network.class);
+        mNetworkCapabilities = in.readParcelable(null, android.net.NetworkCapabilities.class);
+        mLinkProperties = in.readParcelable(null, android.net.LinkProperties.class);
         mSubscriberId = in.readString();
         mLegacyType = in.readInt();
     }
diff --git a/core/java/android/net/NetworkStats.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
similarity index 99%
rename from core/java/android/net/NetworkStats.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
index 46c83df..c7ffc19 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStats.java
@@ -220,8 +220,10 @@
     // TODO: move fields to "mVariable" notation
 
     /**
-     * {@link SystemClock#elapsedRealtime()} timestamp when this data was
+     * {@link SystemClock#elapsedRealtime()} timestamp in milliseconds when this data was
      * generated.
+     * It's a timestamps delta when {@link #subtract()},
+     * {@code INetworkStatsSession#getSummaryForAllUid()} methods are used.
      */
     private long elapsedRealtime;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
similarity index 97%
rename from services/core/java/com/android/server/net/NetworkStatsAccess.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
index d25eae4..3885a9e 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsAccess.java
@@ -11,10 +11,10 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT 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.server.net;
+package android.net;
 
 import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
 import static android.net.NetworkStats.UID_ALL;
@@ -37,7 +37,11 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-/** Utility methods for controlling access to network stats APIs. */
+/**
+ * Utility methods for controlling access to network stats APIs.
+ *
+ * @hide
+ */
 public final class NetworkStatsAccess {
     private NetworkStatsAccess() {}
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
similarity index 95%
rename from services/core/java/com/android/server/net/NetworkStatsCollection.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
index df372b1..0d3b9ed 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.net;
+package android.net;
 
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -31,13 +31,7 @@
 import static android.text.format.DateUtils.WEEK_IN_MILLIS;
 
 import static com.android.internal.net.NetworkUtilsInternal.multiplySafeByRational;
-import static com.android.server.net.NetworkStatsService.TAG;
 
-import android.net.NetworkIdentity;
-import android.net.NetworkStats;
-import android.net.NetworkStatsHistory;
-import android.net.NetworkTemplate;
-import android.net.TrafficStats;
 import android.os.Binder;
 import android.service.NetworkStatsCollectionKeyProto;
 import android.service.NetworkStatsCollectionProto;
@@ -59,16 +53,15 @@
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
 
-import libcore.io.IoUtils;
-
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import libcore.io.IoUtils;
+
 import java.io.BufferedInputStream;
 import java.io.DataInput;
 import java.io.DataInputStream;
 import java.io.DataOutput;
-import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -86,8 +79,11 @@
 /**
  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
  * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
+ *
+ * @hide
  */
 public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
+    private static final String TAG = NetworkStatsCollection.class.getSimpleName();
     /** File header magic number: "ANET" */
     private static final int FILE_MAGIC = 0x414E4554;
 
@@ -335,7 +331,13 @@
 
     /**
      * Summarize all {@link NetworkStatsHistory} in this collection which match
-     * the requested parameters.
+     * the requested parameters across the requested range.
+     *
+     * @param template - a predicate for filtering netstats.
+     * @param start - start of the range, timestamp in milliseconds since the epoch.
+     * @param end - end of the range, timestamp in milliseconds since the epoch.
+     * @param accessLevel - caller access level.
+     * @param callerUid - caller UID.
      */
     public NetworkStats getSummary(NetworkTemplate template, long start, long end,
             @NetworkStatsAccess.Level int accessLevel, int callerUid) {
@@ -361,8 +363,8 @@
                 entry.uid = key.uid;
                 entry.set = key.set;
                 entry.tag = key.tag;
-                entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork() ?
-                        DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
+                entry.defaultNetwork = key.ident.areAllMembersOnDefaultNetwork()
+                        ? DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
                 entry.metered = key.ident.isAnyMemberMetered() ? METERED_YES : METERED_NO;
                 entry.roaming = key.ident.isAnyMemberRoaming() ? ROAMING_YES : ROAMING_NO;
                 entry.rxBytes = historyEntry.rxBytes;
@@ -516,6 +518,12 @@
         }
     }
 
+    /**
+     * Read legacy network summary statistics file format into the collection,
+     * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
+     *
+     * @deprecated
+     */
     @Deprecated
     public void readLegacyNetwork(File file) throws IOException {
         final AtomicFile inputFile = new AtomicFile(file);
@@ -555,6 +563,12 @@
         }
     }
 
+    /**
+     * Read legacy Uid statistics file format into the collection,
+     * See {@code NetworkStatsService#maybeUpgradeLegacyStatsLocked}.
+     *
+     * @deprecated
+     */
     @Deprecated
     public void readLegacyUid(File file, boolean onlyTags) throws IOException {
         final AtomicFile inputFile = new AtomicFile(file);
@@ -769,19 +783,19 @@
         public final int set;
         public final int tag;
 
-        private final int hashCode;
+        private final int mHashCode;
 
-        public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
+        Key(NetworkIdentitySet ident, int uid, int set, int tag) {
             this.ident = ident;
             this.uid = uid;
             this.set = set;
             this.tag = tag;
-            hashCode = Objects.hash(ident, uid, set, tag);
+            mHashCode = Objects.hash(ident, uid, set, tag);
         }
 
         @Override
         public int hashCode() {
-            return hashCode;
+            return mHashCode;
         }
 
         @Override
diff --git a/core/java/android/net/NetworkStatsHistory.aidl b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.aidl
similarity index 100%
rename from core/java/android/net/NetworkStatsHistory.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.aidl
diff --git a/core/java/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
similarity index 97%
rename from core/java/android/net/NetworkStatsHistory.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
index f413063..a875e1a 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java
@@ -526,6 +526,13 @@
     /**
      * Return interpolated data usage across the requested range. Interpolates
      * across buckets, so values may be rounded slightly.
+     *
+     * <p>If the active bucket is not completed yet, it returns the proportional value of it
+     * based on its duration and the {@code end} param.
+     *
+     * @param start - start of the range, timestamp in milliseconds since the epoch.
+     * @param end - end of the range, timestamp in milliseconds since the epoch.
+     * @param recycle - entry instance for performance, could be null.
      */
     @UnsupportedAppUsage
     public Entry getValues(long start, long end, Entry recycle) {
@@ -535,6 +542,11 @@
     /**
      * Return interpolated data usage across the requested range. Interpolates
      * across buckets, so values may be rounded slightly.
+     *
+     * @param start - start of the range, timestamp in milliseconds since the epoch.
+     * @param end - end of the range, timestamp in milliseconds since the epoch.
+     * @param now - current timestamp in milliseconds since the epoch (wall clock).
+     * @param recycle - entry instance for performance, could be null.
      */
     @UnsupportedAppUsage
     public Entry getValues(long start, long end, long now, Entry recycle) {
diff --git a/core/java/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
similarity index 91%
rename from core/java/android/net/NetworkTemplate.java
rename to packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
index c906a13..8b9c14d 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java
@@ -44,16 +44,10 @@
 import android.telephony.Annotation.NetworkType;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.BackupUtils;
-import android.util.Log;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.net.module.util.NetworkIdentityUtils;
 
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -69,19 +63,6 @@
 public class NetworkTemplate implements Parcelable {
     private static final String TAG = "NetworkTemplate";
 
-    /**
-     * Initial Version of the backup serializer.
-     */
-    public static final int BACKUP_VERSION_1_INIT = 1;
-    /**
-     * Version of the backup serializer that added carrier template support.
-     */
-    public static final int BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2;
-    /**
-     * Current Version of the Backup Serializer.
-     */
-    private static final int BACKUP_VERSION = BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE;
-
     public static final int MATCH_MOBILE = 1;
     public static final int MATCH_WIFI = 4;
     public static final int MATCH_ETHERNET = 5;
@@ -849,58 +830,4 @@
             return new NetworkTemplate[size];
         }
     };
-
-    public byte[] getBytesForBackup() throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        DataOutputStream out = new DataOutputStream(baos);
-
-        if (!isPersistable()) {
-            Log.wtf(TAG, "Trying to backup non-persistable template: " + this);
-        }
-
-        out.writeInt(BACKUP_VERSION);
-
-        out.writeInt(mMatchRule);
-        BackupUtils.writeString(out, mSubscriberId);
-        BackupUtils.writeString(out, mNetworkId);
-        out.writeInt(mMetered);
-        out.writeInt(mSubscriberIdMatchRule);
-
-        return baos.toByteArray();
-    }
-
-    public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
-            throws IOException, BackupUtils.BadVersionException {
-        int version = in.readInt();
-        if (version < BACKUP_VERSION_1_INIT || version > BACKUP_VERSION) {
-            throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
-        }
-
-        int matchRule = in.readInt();
-        String subscriberId = BackupUtils.readString(in);
-        String networkId = BackupUtils.readString(in);
-
-        final int metered;
-        final int subscriberIdMatchRule;
-        if (version >= BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) {
-            metered = in.readInt();
-            subscriberIdMatchRule = in.readInt();
-        } else {
-            // For backward compatibility, fill the missing filters from match rules.
-            metered = (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD
-                    || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL;
-            subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT;
-        }
-
-        try {
-            return new NetworkTemplate(matchRule,
-                    subscriberId, new String[] { subscriberId },
-                    networkId, metered, NetworkStats.ROAMING_ALL,
-                    NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
-                    NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
-        } catch (IllegalArgumentException e) {
-            throw new BackupUtils.BadVersionException(
-                    "Restored network template contains unknown match rule " + matchRule, e);
-        }
-    }
 }
diff --git a/core/java/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
similarity index 100%
rename from core/java/android/net/TrafficStats.java
rename to packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java
diff --git a/core/java/android/net/UnderlyingNetworkInfo.aidl b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.aidl
similarity index 100%
rename from core/java/android/net/UnderlyingNetworkInfo.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.aidl
diff --git a/core/java/android/net/UnderlyingNetworkInfo.java b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
similarity index 97%
rename from core/java/android/net/UnderlyingNetworkInfo.java
rename to packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
index 33f9375..7ab53b1 100644
--- a/core/java/android/net/UnderlyingNetworkInfo.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/UnderlyingNetworkInfo.java
@@ -60,7 +60,7 @@
         mOwnerUid = in.readInt();
         mIface = in.readString();
         List<String> underlyingIfaces = new ArrayList<>();
-        in.readList(underlyingIfaces, null /*classLoader*/);
+        in.readList(underlyingIfaces, null /*classLoader*/, java.lang.String.class);
         mUnderlyingIfaces = Collections.unmodifiableList(underlyingIfaces);
     }
 
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/packages/ConnectivityT/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl
similarity index 100%
rename from core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/netstats/provider/INetworkStatsProvider.aidl
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
similarity index 100%
rename from core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/packages/ConnectivityT/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
similarity index 100%
rename from core/java/android/net/netstats/provider/NetworkStatsProvider.java
rename to packages/ConnectivityT/framework-t/src/android/net/netstats/provider/NetworkStatsProvider.java
diff --git a/packages/Nsd/framework/src/android/net/nsd/INsdManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/nsd/INsdManager.aidl
similarity index 100%
rename from packages/Nsd/framework/src/android/net/nsd/INsdManager.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/nsd/INsdManager.aidl
diff --git a/packages/Nsd/framework/src/android/net/nsd/INsdManagerCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
similarity index 100%
rename from packages/Nsd/framework/src/android/net/nsd/INsdManagerCallback.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/nsd/INsdManagerCallback.aidl
diff --git a/packages/Nsd/framework/src/android/net/nsd/INsdServiceConnector.aidl b/packages/ConnectivityT/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
similarity index 100%
rename from packages/Nsd/framework/src/android/net/nsd/INsdServiceConnector.aidl
rename to packages/ConnectivityT/framework-t/src/android/net/nsd/INsdServiceConnector.aidl
diff --git a/packages/Nsd/framework/src/android/net/nsd/NsdManager.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
similarity index 95%
rename from packages/Nsd/framework/src/android/net/nsd/NsdManager.java
rename to packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
index 6c597e2..0f21e55 100644
--- a/packages/Nsd/framework/src/android/net/nsd/NsdManager.java
+++ b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdManager.java
@@ -16,10 +16,6 @@
 
 package android.net.nsd;
 
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.internal.util.Preconditions.checkStringNotEmpty;
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemService;
@@ -32,11 +28,13 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Protocol;
+
+import java.util.Objects;
 
 /**
  * The Network Service Discovery Manager class provides the API to discover services
@@ -175,65 +173,63 @@
      */
     public static final int NSD_STATE_ENABLED = 2;
 
-    private static final int BASE = Protocol.BASE_NSD_MANAGER;
+    /** @hide */
+    public static final int DISCOVER_SERVICES                       = 1;
+    /** @hide */
+    public static final int DISCOVER_SERVICES_STARTED               = 2;
+    /** @hide */
+    public static final int DISCOVER_SERVICES_FAILED                = 3;
+    /** @hide */
+    public static final int SERVICE_FOUND                           = 4;
+    /** @hide */
+    public static final int SERVICE_LOST                            = 5;
 
     /** @hide */
-    public static final int DISCOVER_SERVICES                       = BASE + 1;
+    public static final int STOP_DISCOVERY                          = 6;
     /** @hide */
-    public static final int DISCOVER_SERVICES_STARTED               = BASE + 2;
+    public static final int STOP_DISCOVERY_FAILED                   = 7;
     /** @hide */
-    public static final int DISCOVER_SERVICES_FAILED                = BASE + 3;
-    /** @hide */
-    public static final int SERVICE_FOUND                           = BASE + 4;
-    /** @hide */
-    public static final int SERVICE_LOST                            = BASE + 5;
+    public static final int STOP_DISCOVERY_SUCCEEDED                = 8;
 
     /** @hide */
-    public static final int STOP_DISCOVERY                          = BASE + 6;
+    public static final int REGISTER_SERVICE                        = 9;
     /** @hide */
-    public static final int STOP_DISCOVERY_FAILED                   = BASE + 7;
+    public static final int REGISTER_SERVICE_FAILED                 = 10;
     /** @hide */
-    public static final int STOP_DISCOVERY_SUCCEEDED                = BASE + 8;
+    public static final int REGISTER_SERVICE_SUCCEEDED              = 11;
 
     /** @hide */
-    public static final int REGISTER_SERVICE                        = BASE + 9;
+    public static final int UNREGISTER_SERVICE                      = 12;
     /** @hide */
-    public static final int REGISTER_SERVICE_FAILED                 = BASE + 10;
+    public static final int UNREGISTER_SERVICE_FAILED               = 13;
     /** @hide */
-    public static final int REGISTER_SERVICE_SUCCEEDED              = BASE + 11;
+    public static final int UNREGISTER_SERVICE_SUCCEEDED            = 14;
 
     /** @hide */
-    public static final int UNREGISTER_SERVICE                      = BASE + 12;
+    public static final int RESOLVE_SERVICE                         = 15;
     /** @hide */
-    public static final int UNREGISTER_SERVICE_FAILED               = BASE + 13;
+    public static final int RESOLVE_SERVICE_FAILED                  = 16;
     /** @hide */
-    public static final int UNREGISTER_SERVICE_SUCCEEDED            = BASE + 14;
+    public static final int RESOLVE_SERVICE_SUCCEEDED               = 17;
 
     /** @hide */
-    public static final int RESOLVE_SERVICE                         = BASE + 18;
-    /** @hide */
-    public static final int RESOLVE_SERVICE_FAILED                  = BASE + 19;
-    /** @hide */
-    public static final int RESOLVE_SERVICE_SUCCEEDED               = BASE + 20;
+    public static final int DAEMON_CLEANUP                          = 18;
 
     /** @hide */
-    public static final int DAEMON_CLEANUP                          = BASE + 21;
+    public static final int DAEMON_STARTUP                          = 19;
 
     /** @hide */
-    public static final int DAEMON_STARTUP                          = BASE + 22;
+    public static final int ENABLE                                  = 20;
+    /** @hide */
+    public static final int DISABLE                                 = 21;
 
     /** @hide */
-    public static final int ENABLE                                  = BASE + 24;
-    /** @hide */
-    public static final int DISABLE                                 = BASE + 25;
+    public static final int NATIVE_DAEMON_EVENT                     = 22;
 
     /** @hide */
-    public static final int NATIVE_DAEMON_EVENT                     = BASE + 26;
-
+    public static final int REGISTER_CLIENT                         = 23;
     /** @hide */
-    public static final int REGISTER_CLIENT                         = BASE + 27;
-    /** @hide */
-    public static final int UNREGISTER_CLIENT                       = BASE + 28;
+    public static final int UNREGISTER_CLIENT                       = 24;
 
     /** Dns based service discovery protocol */
     public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -550,7 +546,9 @@
         final int key;
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            checkArgument(valueIndex == -1, "listener already in use");
+            if (valueIndex != -1) {
+                throw new IllegalArgumentException("listener already in use");
+            }
             key = nextListenerKey();
             mListenerMap.put(key, listener);
             mServiceMap.put(key, s);
@@ -569,7 +567,9 @@
         checkListener(listener);
         synchronized (mMapLock) {
             int valueIndex = mListenerMap.indexOfValue(listener);
-            checkArgument(valueIndex != -1, "listener not registered");
+            if (valueIndex == -1) {
+                throw new IllegalArgumentException("listener not registered");
+            }
             return mListenerMap.keyAt(valueIndex);
         }
     }
@@ -598,7 +598,9 @@
      */
     public void registerService(NsdServiceInfo serviceInfo, int protocolType,
             RegistrationListener listener) {
-        checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
+        if (serviceInfo.getPort() <= 0) {
+            throw new IllegalArgumentException("Invalid port number");
+        }
         checkServiceInfo(serviceInfo);
         checkProtocol(protocolType);
         int key = putListener(listener, serviceInfo);
@@ -660,7 +662,9 @@
      * Cannot be null. Cannot be in use for an active service discovery.
      */
     public void discoverServices(String serviceType, int protocolType, DiscoveryListener listener) {
-        checkStringNotEmpty(serviceType, "Service type cannot be empty");
+        if (TextUtils.isEmpty(serviceType)) {
+            throw new IllegalArgumentException("Service type cannot be empty");
+        }
         checkProtocol(protocolType);
 
         NsdServiceInfo s = new NsdServiceInfo();
@@ -719,16 +723,22 @@
     }
 
     private static void checkListener(Object listener) {
-        checkNotNull(listener, "listener cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
     }
 
     private static void checkProtocol(int protocolType) {
-        checkArgument(protocolType == PROTOCOL_DNS_SD, "Unsupported protocol");
+        if (protocolType != PROTOCOL_DNS_SD) {
+            throw new IllegalArgumentException("Unsupported protocol");
+        }
     }
 
     private static void checkServiceInfo(NsdServiceInfo serviceInfo) {
-        checkNotNull(serviceInfo, "NsdServiceInfo cannot be null");
-        checkStringNotEmpty(serviceInfo.getServiceName(), "Service name cannot be empty");
-        checkStringNotEmpty(serviceInfo.getServiceType(), "Service type cannot be empty");
+        Objects.requireNonNull(serviceInfo, "NsdServiceInfo cannot be null");
+        if (TextUtils.isEmpty(serviceInfo.getServiceName())) {
+            throw new IllegalArgumentException("Service name cannot be empty");
+        }
+        if (TextUtils.isEmpty(serviceInfo.getServiceType())) {
+            throw new IllegalArgumentException("Service type cannot be empty");
+        }
     }
 }
diff --git a/packages/Nsd/framework/src/android/net/nsd/NsdServiceInfo.java b/packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java
similarity index 100%
rename from packages/Nsd/framework/src/android/net/nsd/NsdServiceInfo.java
rename to packages/ConnectivityT/framework-t/src/android/net/nsd/NsdServiceInfo.java
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
new file mode 100644
index 0000000..97dfb64
--- /dev/null
+++ b/packages/ConnectivityT/service/Android.bp
@@ -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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// NetworkStats related libraries.
+
+filegroup {
+    name: "services.connectivity-netstats-sources",
+    srcs: [
+        "src/com/android/server/net/NetworkIdentity*.java",
+        "src/com/android/server/net/NetworkStats*.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// Nsd related libraries.
+
+filegroup {
+    name: "services.connectivity-nsd-sources",
+    srcs: [
+        "src/com/android/server/INativeDaemon*.java",
+        "src/com/android/server/NativeDaemon*.java",
+        "src/com/android/server/Nsd*.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// IpSec related libraries.
+
+filegroup {
+    name: "services.connectivity-ipsec-sources",
+    srcs: [
+        "src/com/android/server/IpSecService.java",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
+// Ethernet related libraries.
+
+filegroup {
+    name: "services.connectivity-ethernet-sources",
+    srcs: [
+        "src/com/android/server/net/IpConfigStore.java",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/opt/net/ethernet",
+    ],
+}
+
+// Connectivity-T common libraries.
+
+filegroup {
+    name: "services.connectivity-tiramisu-sources",
+    srcs: [
+        ":services.connectivity-ethernet-sources",
+        ":services.connectivity-ipsec-sources",
+        ":services.connectivity-netstats-sources",
+        ":services.connectivity-nsd-sources",
+    ],
+    path: "src",
+    visibility: ["//frameworks/base/services/core"],
+}
diff --git a/packages/Nsd/service/src/com/android/server/INativeDaemonConnectorCallbacks.java b/packages/ConnectivityT/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
similarity index 100%
rename from packages/Nsd/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
rename to packages/ConnectivityT/service/src/com/android/server/INativeDaemonConnectorCallbacks.java
diff --git a/services/core/java/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
similarity index 90%
rename from services/core/java/com/android/server/IpSecService.java
rename to packages/ConnectivityT/service/src/com/android/server/IpSecService.java
index aeb8143..d1e432e 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java
@@ -45,7 +45,6 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.TrafficStats;
-import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -96,8 +95,6 @@
 public class IpSecService extends IIpSecService.Stub {
     private static final String TAG = "IpSecService";
     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
-
-    private static final String NETD_SERVICE_NAME = "netd";
     private static final int[] ADDRESS_FAMILIES =
             new int[] {OsConstants.AF_INET, OsConstants.AF_INET6};
 
@@ -106,6 +103,8 @@
 
     @VisibleForTesting static final int MAX_PORT_BIND_ATTEMPTS = 10;
 
+    private final INetd mNetd;
+
     static {
         try {
             INADDR_ANY = InetAddress.getByAddress(new byte[] {0, 0, 0, 0});
@@ -119,6 +118,7 @@
 
     /* Binder context for this service */
     private final Context mContext;
+    private final Dependencies mDeps;
 
     /**
      * The next non-repeating global ID for tracking resources between users, this service, and
@@ -129,23 +129,24 @@
     @GuardedBy("IpSecService.this")
     private int mNextResourceId = 1;
 
-    interface IpSecServiceConfiguration {
-        INetd getNetdInstance() throws RemoteException;
-
-        static IpSecServiceConfiguration GETSRVINSTANCE =
-                new IpSecServiceConfiguration() {
-                    @Override
-                    public INetd getNetdInstance() throws RemoteException {
-                        final INetd netd = NetdService.getInstance();
-                        if (netd == null) {
-                            throw new RemoteException("Failed to Get Netd Instance");
-                        }
-                        return netd;
-                    }
-                };
+    /**
+     * Dependencies of IpSecService, for injection in tests.
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * Get a reference to INetd.
+         */
+        public INetd getNetdInstance(Context context) throws RemoteException {
+            final INetd netd = INetd.Stub.asInterface((IBinder)
+                    context.getSystemService(Context.NETD_SERVICE));
+            if (netd == null) {
+                throw new RemoteException("Failed to Get Netd Instance");
+            }
+            return netd;
+        }
     }
 
-    private final IpSecServiceConfiguration mSrvConfig;
     final UidFdTagger mUidFdTagger;
 
     /**
@@ -491,8 +492,8 @@
      * <p>This class associates kernel resources with the UID that owns and controls them.
      */
     private abstract class OwnedResourceRecord implements IResource {
-        final int pid;
-        final int uid;
+        final int mPid;
+        final int mUid;
         protected final int mResourceId;
 
         OwnedResourceRecord(int resourceId) {
@@ -501,8 +502,8 @@
                 throw new IllegalArgumentException("Resource ID must not be INVALID_RESOURCE_ID");
             }
             mResourceId = resourceId;
-            pid = Binder.getCallingPid();
-            uid = Binder.getCallingUid();
+            mPid = Binder.getCallingPid();
+            mUid = Binder.getCallingUid();
 
             getResourceTracker().take();
         }
@@ -512,7 +513,7 @@
 
         /** Convenience method; retrieves the user resource record for the stored UID. */
         protected UserRecord getUserRecord() {
-            return mUserResourceTracker.getUserRecord(uid);
+            return mUserResourceTracker.getUserRecord(mUid);
         }
 
         @Override
@@ -527,9 +528,9 @@
                     .append("{mResourceId=")
                     .append(mResourceId)
                     .append(", pid=")
-                    .append(pid)
+                    .append(mPid)
                     .append(", uid=")
-                    .append(uid)
+                    .append(mUid)
                     .append("}")
                     .toString();
         }
@@ -545,7 +546,7 @@
         SparseArray<RefcountedResource<T>> mArray = new SparseArray<>();
         private final String mTypeName;
 
-        public RefcountedResourceArray(String typeName) {
+        RefcountedResourceArray(String typeName) {
             this.mTypeName = typeName;
         }
 
@@ -625,16 +626,14 @@
         public void freeUnderlyingResources() {
             int spi = mSpi.getSpi();
             try {
-                mSrvConfig
-                        .getNetdInstance()
-                        .ipSecDeleteSecurityAssociation(
-                                uid,
-                                mConfig.getSourceAddress(),
-                                mConfig.getDestinationAddress(),
-                                spi,
-                                mConfig.getMarkValue(),
-                                mConfig.getMarkMask(),
-                                mConfig.getXfrmInterfaceId());
+                mNetd.ipSecDeleteSecurityAssociation(
+                        mUid,
+                        mConfig.getSourceAddress(),
+                        mConfig.getDestinationAddress(),
+                        spi,
+                        mConfig.getMarkValue(),
+                        mConfig.getMarkMask(),
+                        mConfig.getXfrmInterfaceId());
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Failed to delete SA with ID: " + mResourceId, e);
             }
@@ -681,7 +680,8 @@
 
         private boolean mOwnedByTransform = false;
 
-        SpiRecord(int resourceId, String sourceAddress, String destinationAddress, int spi) {
+        SpiRecord(int resourceId, String sourceAddress,
+                String destinationAddress, int spi) {
             super(resourceId);
             mSourceAddress = sourceAddress;
             mDestinationAddress = destinationAddress;
@@ -693,11 +693,9 @@
         public void freeUnderlyingResources() {
             try {
                 if (!mOwnedByTransform) {
-                    mSrvConfig
-                            .getNetdInstance()
-                            .ipSecDeleteSecurityAssociation(
-                                    uid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
-                                    0 /* mask */, 0 /* if_id */);
+                    mNetd.ipSecDeleteSecurityAssociation(
+                            mUid, mSourceAddress, mDestinationAddress, mSpi, 0 /* mark */,
+                            0 /* mask */, 0 /* if_id */);
                 }
             } catch (ServiceSpecificException | RemoteException e) {
                 Log.e(TAG, "Failed to delete SPI reservation with ID: " + mResourceId, e);
@@ -844,19 +842,18 @@
             //       Teardown VTI
             //       Delete global policies
             try {
-                final INetd netd = mSrvConfig.getNetdInstance();
-                netd.ipSecRemoveTunnelInterface(mInterfaceName);
+                mNetd.ipSecRemoveTunnelInterface(mInterfaceName);
 
                 for (int selAddrFamily : ADDRESS_FAMILIES) {
-                    netd.ipSecDeleteSecurityPolicy(
-                            uid,
+                    mNetd.ipSecDeleteSecurityPolicy(
+                            mUid,
                             selAddrFamily,
                             IpSecManager.DIRECTION_OUT,
                             mOkey,
                             0xffffffff,
                             mIfId);
-                    netd.ipSecDeleteSecurityPolicy(
-                            uid,
+                    mNetd.ipSecDeleteSecurityPolicy(
+                            mUid,
                             selAddrFamily,
                             IpSecManager.DIRECTION_IN,
                             mIkey,
@@ -1012,29 +1009,28 @@
      * @param context Binder context for this service
      */
     private IpSecService(Context context) {
-        this(context, IpSecServiceConfiguration.GETSRVINSTANCE);
+        this(context, new Dependencies());
     }
 
     static IpSecService create(Context context)
             throws InterruptedException {
         final IpSecService service = new IpSecService(context);
-        service.connectNativeNetdService();
         return service;
     }
 
     @NonNull
     private AppOpsManager getAppOpsManager() {
         AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-        if(appOps == null) throw new RuntimeException("System Server couldn't get AppOps");
+        if (appOps == null) throw new RuntimeException("System Server couldn't get AppOps");
         return appOps;
     }
 
     /** @hide */
     @VisibleForTesting
-    public IpSecService(Context context, IpSecServiceConfiguration config) {
+    public IpSecService(Context context, Dependencies deps) {
         this(
                 context,
-                config,
+                deps,
                 (fd, uid) -> {
                     try {
                         TrafficStats.setThreadStatsUid(uid);
@@ -1047,13 +1043,18 @@
 
     /** @hide */
     @VisibleForTesting
-    public IpSecService(Context context, IpSecServiceConfiguration config,
-            UidFdTagger uidFdTagger) {
+    public IpSecService(Context context, Dependencies deps, UidFdTagger uidFdTagger) {
         mContext = context;
-        mSrvConfig = config;
+        mDeps = Objects.requireNonNull(deps, "Missing dependencies.");
         mUidFdTagger = uidFdTagger;
+        try {
+            mNetd = mDeps.getNetdInstance(mContext);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
+    /** Called by system server when system is ready. */
     public void systemReady() {
         if (isNetdAlive()) {
             Log.d(TAG, "IpSecService is ready");
@@ -1062,25 +1063,12 @@
         }
     }
 
-    private void connectNativeNetdService() {
-        // Avoid blocking the system server to do this
-        new Thread() {
-            @Override
-            public void run() {
-                synchronized (IpSecService.this) {
-                    NetdService.get(NETD_FETCH_TIMEOUT_MS);
-                }
-            }
-        }.start();
-    }
-
     synchronized boolean isNetdAlive() {
         try {
-            final INetd netd = mSrvConfig.getNetdInstance();
-            if (netd == null) {
+            if (mNetd == null) {
                 return false;
             }
-            return netd.isAlive();
+            return mNetd.isAlive();
         } catch (RemoteException re) {
             return false;
         }
@@ -1141,15 +1129,13 @@
                         IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
             }
 
-            spi =
-                    mSrvConfig
-                            .getNetdInstance()
-                            .ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
+            spi = mNetd.ipSecAllocateSpi(callingUid, "", destinationAddress, requestedSpi);
             Log.d(TAG, "Allocated SPI " + spi);
             userRecord.mSpiRecords.put(
                     resourceId,
                     new RefcountedResource<SpiRecord>(
-                            new SpiRecord(resourceId, "", destinationAddress, spi), binder));
+                            new SpiRecord(resourceId, "",
+                            destinationAddress, spi), binder));
         } catch (ServiceSpecificException e) {
             if (e.errorCode == OsConstants.ENOENT) {
                 return new IpSecSpiResponse(
@@ -1229,7 +1215,7 @@
          * <p>Since the socket is created on behalf of an unprivileged application, all traffic
          * should be accounted to the UID of the unprivileged application.
          */
-        public void tag(FileDescriptor fd, int uid) throws IOException;
+        void tag(FileDescriptor fd, int uid) throws IOException;
     }
 
     /**
@@ -1266,8 +1252,7 @@
                     OsConstants.UDP_ENCAP,
                     OsConstants.UDP_ENCAP_ESPINUDP);
 
-            mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(
-                        new ParcelFileDescriptor(sockFd), callingUid);
+            mNetd.ipSecSetEncapSocketOwner(new ParcelFileDescriptor(sockFd), callingUid);
             if (port != 0) {
                 Log.v(TAG, "Binding to port " + port);
                 Os.bind(sockFd, INADDR_ANY, port);
@@ -1329,16 +1314,15 @@
             //       Create VTI
             //       Add inbound/outbound global policies
             //              (use reqid = 0)
-            final INetd netd = mSrvConfig.getNetdInstance();
-            netd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
+            mNetd.ipSecAddTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey, resourceId);
 
             BinderUtils.withCleanCallingIdentity(() -> {
-                NetdUtils.setInterfaceUp(netd, intfName);
+                NetdUtils.setInterfaceUp(mNetd, intfName);
             });
 
             for (int selAddrFamily : ADDRESS_FAMILIES) {
                 // Always send down correct local/remote addresses for template.
-                netd.ipSecAddSecurityPolicy(
+                mNetd.ipSecAddSecurityPolicy(
                         callerUid,
                         selAddrFamily,
                         IpSecManager.DIRECTION_OUT,
@@ -1348,7 +1332,7 @@
                         okey,
                         0xffffffff,
                         resourceId);
-                netd.ipSecAddSecurityPolicy(
+                mNetd.ipSecAddSecurityPolicy(
                         callerUid,
                         selAddrFamily,
                         IpSecManager.DIRECTION_IN,
@@ -1368,7 +1352,7 @@
                 //
                 // This is necessary only on the tunnel interface, and not any the interface to
                 // which traffic will be forwarded to.
-                netd.ipSecAddSecurityPolicy(
+                mNetd.ipSecAddSecurityPolicy(
                         callerUid,
                         selAddrFamily,
                         IpSecManager.DIRECTION_FWD,
@@ -1425,12 +1409,10 @@
         try {
             // We can assume general validity of the IP address, since we get them as a
             // LinkAddress, which does some validation.
-            mSrvConfig
-                    .getNetdInstance()
-                    .interfaceAddAddress(
-                            tunnelInterfaceInfo.mInterfaceName,
-                            localAddr.getAddress().getHostAddress(),
-                            localAddr.getPrefixLength());
+            mNetd.interfaceAddAddress(
+                    tunnelInterfaceInfo.mInterfaceName,
+                    localAddr.getAddress().getHostAddress(),
+                    localAddr.getPrefixLength());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1454,9 +1436,7 @@
         try {
             // We can assume general validity of the IP address, since we get them as a
             // LinkAddress, which does some validation.
-            mSrvConfig
-                    .getNetdInstance()
-                    .interfaceDelAddress(
+            mNetd.interfaceDelAddress(
                             tunnelInterfaceInfo.mInterfaceName,
                             localAddr.getAddress().getHostAddress(),
                             localAddr.getPrefixLength());
@@ -1669,30 +1649,28 @@
             cryptName = crypt.getName();
         }
 
-        mSrvConfig
-                .getNetdInstance()
-                .ipSecAddSecurityAssociation(
-                        Binder.getCallingUid(),
-                        c.getMode(),
-                        c.getSourceAddress(),
-                        c.getDestinationAddress(),
-                        (c.getNetwork() != null) ? c.getNetwork().getNetId() : 0,
-                        spiRecord.getSpi(),
-                        c.getMarkValue(),
-                        c.getMarkMask(),
-                        (auth != null) ? auth.getName() : "",
-                        (auth != null) ? auth.getKey() : new byte[] {},
-                        (auth != null) ? auth.getTruncationLengthBits() : 0,
-                        cryptName,
-                        (crypt != null) ? crypt.getKey() : new byte[] {},
-                        (crypt != null) ? crypt.getTruncationLengthBits() : 0,
-                        (authCrypt != null) ? authCrypt.getName() : "",
-                        (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
-                        (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
-                        encapType,
-                        encapLocalPort,
-                        encapRemotePort,
-                        c.getXfrmInterfaceId());
+        mNetd.ipSecAddSecurityAssociation(
+                Binder.getCallingUid(),
+                c.getMode(),
+                c.getSourceAddress(),
+                c.getDestinationAddress(),
+                (c.getNetwork() != null) ? c.getNetwork().getNetId() : 0,
+                spiRecord.getSpi(),
+                c.getMarkValue(),
+                c.getMarkMask(),
+                (auth != null) ? auth.getName() : "",
+                (auth != null) ? auth.getKey() : new byte[] {},
+                (auth != null) ? auth.getTruncationLengthBits() : 0,
+                cryptName,
+                (crypt != null) ? crypt.getKey() : new byte[] {},
+                (crypt != null) ? crypt.getTruncationLengthBits() : 0,
+                (authCrypt != null) ? authCrypt.getName() : "",
+                (authCrypt != null) ? authCrypt.getKey() : new byte[] {},
+                (authCrypt != null) ? authCrypt.getTruncationLengthBits() : 0,
+                encapType,
+                encapLocalPort,
+                encapRemotePort,
+                c.getXfrmInterfaceId());
     }
 
     /**
@@ -1771,7 +1749,7 @@
         TransformRecord info = userRecord.mTransformRecords.getResourceOrThrow(resourceId);
 
         // TODO: make this a function.
-        if (info.pid != getCallingPid() || info.uid != callingUid) {
+        if (info.mPid != getCallingPid() || info.mUid != callingUid) {
             throw new SecurityException("Only the owner of an IpSec Transform may apply it!");
         }
 
@@ -1781,15 +1759,13 @@
                 c.getMode() == IpSecTransform.MODE_TRANSPORT,
                 "Transform mode was not Transport mode; cannot be applied to a socket");
 
-        mSrvConfig
-                .getNetdInstance()
-                .ipSecApplyTransportModeTransform(
-                        socket,
-                        callingUid,
-                        direction,
-                        c.getSourceAddress(),
-                        c.getDestinationAddress(),
-                        info.getSpiRecord().getSpi());
+        mNetd.ipSecApplyTransportModeTransform(
+                socket,
+                callingUid,
+                direction,
+                c.getSourceAddress(),
+                c.getDestinationAddress(),
+                info.getSpiRecord().getSpi());
     }
 
     /**
@@ -1801,9 +1777,7 @@
     @Override
     public synchronized void removeTransportModeTransforms(ParcelFileDescriptor socket)
             throws RemoteException {
-        mSrvConfig
-                .getNetdInstance()
-                .ipSecRemoveTransportModeTransform(socket);
+        mNetd.ipSecRemoveTransportModeTransform(socket);
     }
 
     /**
@@ -1878,18 +1852,16 @@
 
             // Always update the policy with the relevant XFRM_IF_ID
             for (int selAddrFamily : ADDRESS_FAMILIES) {
-                mSrvConfig
-                        .getNetdInstance()
-                        .ipSecUpdateSecurityPolicy(
-                                callingUid,
-                                selAddrFamily,
-                                direction,
-                                transformInfo.getConfig().getSourceAddress(),
-                                transformInfo.getConfig().getDestinationAddress(),
-                                spi, // If outbound, also add SPI to the policy.
-                                mark, // Must always set policy mark; ikey/okey for VTIs
-                                0xffffffff,
-                                c.getXfrmInterfaceId());
+                mNetd.ipSecUpdateSecurityPolicy(
+                        callingUid,
+                        selAddrFamily,
+                        direction,
+                        transformInfo.getConfig().getSourceAddress(),
+                        transformInfo.getConfig().getDestinationAddress(),
+                        spi, // If outbound, also add SPI to the policy.
+                        mark, // Must always set policy mark; ikey/okey for VTIs
+                        0xffffffff,
+                        c.getXfrmInterfaceId());
             }
 
             // Update SA with tunnel mark (ikey or okey based on direction)
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java b/packages/ConnectivityT/service/src/com/android/server/NativeDaemonConnector.java
similarity index 96%
rename from packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
rename to packages/ConnectivityT/service/src/com/android/server/NativeDaemonConnector.java
index 02475dd..ec8d779 100644
--- a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
+++ b/packages/ConnectivityT/service/src/com/android/server/NativeDaemonConnector.java
@@ -20,16 +20,15 @@
 import android.net.LocalSocketAddress;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.util.LocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.power.ShutdownThread;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -83,13 +82,6 @@
 
     NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
             int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
-        this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
-                FgThread.get().getLooper());
-    }
-
-    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
-            int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
-            Looper looper) {
         mCallbacks = callbacks;
         mSocket = socket;
         mResponseQueue = new ResponseQueue(responseQueueSize);
@@ -97,10 +89,12 @@
         if (mWakeLock != null) {
             mWakeLock.setReferenceCounted(true);
         }
-        mLooper = looper;
         mSequenceNumber = new AtomicInteger(0);
         TAG = logTag != null ? logTag : "NativeDaemonConnector";
         mLocalLog = new LocalLog(maxLogSize);
+        final HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mLooper = thread.getLooper();
     }
 
     /**
@@ -135,23 +129,15 @@
         mCallbackHandler = new Handler(mLooper, this);
 
         while (true) {
-            if (isShuttingDown()) break;
             try {
                 listenToSocket();
             } catch (Exception e) {
                 loge("Error in NativeDaemonConnector: " + e);
-                if (isShuttingDown()) break;
                 SystemClock.sleep(5000);
             }
         }
     }
 
-    private static boolean isShuttingDown() {
-        String shutdownAct = SystemProperties.get(
-            ShutdownThread.SHUTDOWN_ACTION_PROPERTY, "");
-        return shutdownAct != null && shutdownAct.length() > 0;
-    }
-
     @Override
     public boolean handleMessage(Message msg) {
         final String event = (String) msg.obj;
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonConnectorException.java b/packages/ConnectivityT/service/src/com/android/server/NativeDaemonConnectorException.java
similarity index 100%
rename from packages/Nsd/service/src/com/android/server/NativeDaemonConnectorException.java
rename to packages/ConnectivityT/service/src/com/android/server/NativeDaemonConnectorException.java
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java b/packages/ConnectivityT/service/src/com/android/server/NativeDaemonEvent.java
similarity index 100%
rename from packages/Nsd/service/src/com/android/server/NativeDaemonEvent.java
rename to packages/ConnectivityT/service/src/com/android/server/NativeDaemonEvent.java
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonTimeoutException.java b/packages/ConnectivityT/service/src/com/android/server/NativeDaemonTimeoutException.java
similarity index 100%
rename from packages/Nsd/service/src/com/android/server/NativeDaemonTimeoutException.java
rename to packages/ConnectivityT/service/src/com/android/server/NativeDaemonTimeoutException.java
diff --git a/packages/Nsd/service/src/com/android/server/NsdService.java b/packages/ConnectivityT/service/src/com/android/server/NsdService.java
similarity index 93%
rename from packages/Nsd/service/src/com/android/server/NsdService.java
rename to packages/ConnectivityT/service/src/com/android/server/NsdService.java
index 76ecea7..497107d 100644
--- a/packages/Nsd/service/src/com/android/server/NsdService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/NsdService.java
@@ -16,12 +16,9 @@
 
 package com.android.server;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.net.nsd.INsdManager;
 import android.net.nsd.INsdManagerCallback;
 import android.net.nsd.INsdServiceConnector;
@@ -33,7 +30,6 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Base64;
 import android.util.Log;
 import android.util.Pair;
@@ -66,7 +62,6 @@
     private static final long CLEANUP_DELAY_MS = 10000;
 
     private final Context mContext;
-    private final NsdSettings mNsdSettings;
     private final NsdStateMachine mNsdStateMachine;
     private final DaemonConnection mDaemon;
     private final NativeCallbackReceiver mDaemonCallback;
@@ -121,30 +116,14 @@
             this.removeMessages(NsdManager.DAEMON_CLEANUP);
         }
 
-        /**
-         * Observes the NSD on/off setting, and takes action when changed.
-         */
-        private void registerForNsdSetting() {
-            final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
-                @Override
-                public void onChange(boolean selfChange) {
-                    notifyEnabled(isNsdEnabled());
-                }
-            };
-
-            final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
-            mNsdSettings.registerContentObserver(uri, contentObserver);
-        }
-
         NsdStateMachine(String name, Handler handler) {
             super(name, handler);
             addState(mDefaultState);
                 addState(mDisabledState, mDefaultState);
                 addState(mEnabledState, mDefaultState);
-            State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
+            State initialState = mEnabledState;
             setInitialState(initialState);
             setLogRecSize(25);
-            registerForNsdSetting();
         }
 
         class DefaultState extends State {
@@ -580,11 +559,9 @@
     }
 
     @VisibleForTesting
-    NsdService(Context ctx, NsdSettings settings, Handler handler,
-            DaemonConnectionSupplier fn, long cleanupDelayMs) {
+    NsdService(Context ctx, Handler handler, DaemonConnectionSupplier fn, long cleanupDelayMs) {
         mCleanupDelayMs = cleanupDelayMs;
         mContext = ctx;
-        mNsdSettings = settings;
         mNsdStateMachine = new NsdStateMachine(TAG, handler);
         mNsdStateMachine.start();
         mDaemonCallback = new NativeCallbackReceiver();
@@ -592,12 +569,11 @@
     }
 
     public static NsdService create(Context context) throws InterruptedException {
-        NsdSettings settings = NsdSettings.makeDefault(context);
         HandlerThread thread = new HandlerThread(TAG);
         thread.start();
         Handler handler = new Handler(thread.getLooper());
-        NsdService service = new NsdService(context, settings, handler,
-                DaemonConnection::new, CLEANUP_DELAY_MS);
+        NsdService service =
+                new NsdService(context, handler, DaemonConnection::new, CLEANUP_DELAY_MS);
         service.mDaemonCallback.awaitConnection();
         return service;
     }
@@ -669,10 +645,6 @@
         }
     }
 
-    private void notifyEnabled(boolean isEnabled) {
-        mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
-    }
-
     private void sendNsdStateChangeBroadcast(boolean isEnabled) {
         final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -681,14 +653,6 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    private boolean isNsdEnabled() {
-        boolean ret = mNsdSettings.isEnabled();
-        if (DBG) {
-            Log.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
-        }
-        return ret;
-    }
-
     private int getUniqueId() {
         if (++mUniqueId == INVALID_ID) return ++mUniqueId;
         return mUniqueId;
@@ -1075,35 +1039,4 @@
             }
         }
     }
-
-    /**
-     * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to
-     * override, or have side effects on global state in unit tests.
-     */
-    @VisibleForTesting
-    public interface NsdSettings {
-        boolean isEnabled();
-        void putEnabledStatus(boolean isEnabled);
-        void registerContentObserver(Uri uri, ContentObserver observer);
-
-        static NsdSettings makeDefault(Context context) {
-            final ContentResolver resolver = context.getContentResolver();
-            return new NsdSettings() {
-                @Override
-                public boolean isEnabled() {
-                    return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
-                }
-
-                @Override
-                public void putEnabledStatus(boolean isEnabled) {
-                    Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
-                }
-
-                @Override
-                public void registerContentObserver(Uri uri, ContentObserver observer) {
-                    resolver.registerContentObserver(uri, false, observer);
-                }
-            };
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
similarity index 95%
rename from services/core/java/com/android/server/net/IpConfigStore.java
rename to packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
index d17dbde..3a9a544 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
@@ -44,6 +44,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * This class provides an API to store and manage L3 network IP configuration.
+ */
 public class IpConfigStore {
     private static final String TAG = "IpConfigStore";
     private static final boolean DBG = false;
@@ -78,6 +81,9 @@
         return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION);
     }
 
+    /**
+     *  Write the IP configuration with the given parameters to {@link DataOutputStream}.
+     */
     @VisibleForTesting
     public static boolean writeConfig(DataOutputStream out, String configKey,
                                 IpConfiguration config, int version) throws IOException {
@@ -154,10 +160,10 @@
                     break;
                 case UNASSIGNED:
                     /* Ignore */
-                        break;
-                    default:
-                        loge("Ignore invalid proxy settings while writing");
-                        break;
+                    break;
+                default:
+                    loge("Ignore invalid proxy settings while writing");
+                    break;
             }
 
             if (written) {
@@ -177,7 +183,7 @@
     }
 
     /**
-     * @Deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
+     * @deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
      * New method uses string as network identifier which could be interface name or MAC address or
      * other token.
      */
@@ -186,22 +192,28 @@
                                               final SparseArray<IpConfiguration> networks) {
         mWriter.write(filePath, out -> {
             out.writeInt(IPCONFIG_FILE_VERSION);
-            for(int i = 0; i < networks.size(); i++) {
+            for (int i = 0; i < networks.size(); i++) {
                 writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i));
             }
         });
     }
 
+    /**
+     *  Write the IP configuration associated to the target networks to the destination path.
+     */
     public void writeIpConfigurations(String filePath,
                                       ArrayMap<String, IpConfiguration> networks) {
         mWriter.write(filePath, out -> {
             out.writeInt(IPCONFIG_FILE_VERSION);
-            for(int i = 0; i < networks.size(); i++) {
+            for (int i = 0; i < networks.size(); i++) {
                 writeConfig(out, networks.keyAt(i), networks.valueAt(i));
             }
         });
     }
 
+    /**
+     * Read the IP configuration from the destination path to {@link BufferedInputStream}.
+     */
     public static ArrayMap<String, IpConfiguration> readIpConfigurations(String filePath) {
         BufferedInputStream bufferedInputStream;
         try {
@@ -215,7 +227,7 @@
         return readIpConfigurations(bufferedInputStream);
     }
 
-    /** @Deprecated use {@link #readIpConfigurations(String)} */
+    /** @deprecated use {@link #readIpConfigurations(String)} */
     @Deprecated
     public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
         BufferedInputStream bufferedInputStream;
@@ -230,7 +242,7 @@
         return readIpAndProxyConfigurations(bufferedInputStream);
     }
 
-    /** @Deprecated use {@link #readIpConfigurations(InputStream)} */
+    /** @deprecated use {@link #readIpConfigurations(InputStream)} */
     @Deprecated
     public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
             InputStream inputStream) {
@@ -420,7 +432,7 @@
             if (in != null) {
                 try {
                     in.close();
-                } catch (Exception e) {}
+                } catch (Exception e) { }
             }
         }
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
similarity index 100%
rename from services/core/java/com/android/server/net/NetworkStatsFactory.java
rename to packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsFactory.java
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java
similarity index 100%
rename from services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
rename to packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsManagerInternal.java
diff --git a/services/core/java/com/android/server/net/NetworkStatsObservers.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
similarity index 98%
rename from services/core/java/com/android/server/net/NetworkStatsObservers.java
rename to packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
index 2564dae..1a0866d 100644
--- a/services/core/java/com/android/server/net/NetworkStatsObservers.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsObservers.java
@@ -22,7 +22,10 @@
 
 import android.app.usage.NetworkStatsManager;
 import android.net.DataUsageRequest;
+import android.net.NetworkIdentitySet;
 import android.net.NetworkStats;
+import android.net.NetworkStatsAccess;
+import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.os.Bundle;
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
similarity index 98%
rename from services/core/java/com/android/server/net/NetworkStatsRecorder.java
rename to packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
index 978ae87..5e27c77 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsRecorder.java
@@ -21,8 +21,11 @@
 import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.text.format.DateUtils.YEAR_IN_MILLIS;
 
+import android.net.NetworkIdentitySet;
 import android.net.NetworkStats;
 import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsAccess;
+import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TrafficStats;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
similarity index 99%
rename from services/core/java/com/android/server/net/NetworkStatsService.java
rename to packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
index c876d41..2beca73 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java
@@ -96,11 +96,14 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkIdentity;
+import android.net.NetworkIdentitySet;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
 import android.net.NetworkStateSnapshot;
 import android.net.NetworkStats;
 import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsAccess;
+import android.net.NetworkStatsCollection;
 import android.net.NetworkStatsHistory;
 import android.net.NetworkTemplate;
 import android.net.TelephonyNetworkSpecifier;
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
similarity index 100%
rename from services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
rename to packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
diff --git a/packages/Nsd/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java b/packages/ConnectivityT/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
similarity index 100%
rename from packages/Nsd/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
rename to packages/ConnectivityT/tests/unit/java/com/android/server/NativeDaemonConnectorTest.java
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index 1bc4983..1765348 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -8,6 +8,7 @@
     <uses-permission android:name="android.permission.REBOOT" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.READ_OEM_UNLOCK_STATE" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application
         android:allowBackup="false"
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 9d3ce34..cc7bb4a 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -15,6 +15,8 @@
     <!-- controls -->
     <uses-permission android:name="android.permission.BIND_CONTROLS" />
 
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
     <application
         android:icon="@drawable/icon"
         android:label="@string/app_name">
diff --git a/packages/Nsd/OWNERS b/packages/Nsd/OWNERS
deleted file mode 100644
index 4862377..0000000
--- a/packages/Nsd/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking
\ No newline at end of file
diff --git a/packages/Nsd/framework/Android.bp b/packages/Nsd/framework/Android.bp
deleted file mode 100644
index 2363a9f..0000000
--- a/packages/Nsd/framework/Android.bp
+++ /dev/null
@@ -1,54 +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
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
-    name: "framework-connectivity-nsd-internal-sources",
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.aidl",
-    ],
-    path: "src",
-    visibility: [
-        "//visibility:private",
-    ],
-}
-
-filegroup {
-    name: "framework-connectivity-nsd-aidl-export-sources",
-    srcs: [
-        "aidl-export/**/*.aidl",
-    ],
-    path: "aidl-export",
-    visibility: [
-        "//visibility:private",
-    ],
-}
-
-filegroup {
-    name: "framework-connectivity-nsd-sources",
-    srcs: [
-        ":framework-connectivity-nsd-internal-sources",
-        ":framework-connectivity-nsd-aidl-export-sources",
-    ],
-    visibility: [
-        "//frameworks/base",
-    ],
-}
diff --git a/packages/Nsd/service/Android.bp b/packages/Nsd/service/Android.bp
deleted file mode 100644
index 529f58d..0000000
--- a/packages/Nsd/service/Android.bp
+++ /dev/null
@@ -1,31 +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
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
-    name: "services.connectivity-nsd-sources",
-    srcs: [
-        "src/**/*.java",
-    ],
-    path: "src",
-    visibility: [
-        "//frameworks/base/services/core",
-    ],
-}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 197b7b2..6669d6b 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -17,6 +17,7 @@
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <uses-permission android:name="com.google.android.permission.INSTALL_WEARABLE_PACKAGES" />
 
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index ca3ec3c..688d116 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -112,6 +112,8 @@
     <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_application_text_user">Do you want to uninstall this app for the user <xliff:g id="username">%1$s</xliff:g>?</string>
     <!--  [CHAR LIMIT=none] -->
+    <string name="uninstall_application_text_current_user_work_profile">Do you want to uninstall this app from your work profile?</string>
+    <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_update_text">Replace this app with the factory version? All data will be removed.</string>
     <!--  [CHAR LIMIT=none] -->
     <string name="uninstall_update_text_multiuser">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>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 99f6a92..36294ac 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -125,6 +126,7 @@
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+        final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = UserManager.get(getActivity());
         if (isUpdate) {
             if (isSingleUser(userManager)) {
@@ -135,10 +137,17 @@
         } else {
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
-            } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
+            } else if (!dialogInfo.user.equals(myUserHandle)) {
                 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                messageBuilder.append(
-                        getString(R.string.uninstall_application_text_user, userInfo.name));
+                if (userInfo.isManagedProfile()
+                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_current_user_work_profile,
+                                    userInfo.name));
+                } else {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                }
             } else {
                 messageBuilder.append(getString(R.string.uninstall_application_text));
             }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 21d25f5..2d241ca 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -21,7 +21,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Bundle;
+import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
+
 import androidx.leanback.app.GuidedStepFragment;
 import androidx.leanback.widget.GuidanceStylist;
 import androidx.leanback.widget.GuidedAction;
@@ -59,6 +62,7 @@
 
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
+        final UserHandle myUserHandle = Process.myUserHandle();
         UserManager userManager = UserManager.get(getActivity());
         if (isUpdate) {
             if (isSingleUser(userManager)) {
@@ -69,10 +73,17 @@
         } else {
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
-            } else if (!dialogInfo.user.equals(android.os.Process.myUserHandle())) {
+            } else if (!dialogInfo.user.equals(myUserHandle)) {
                 UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                messageBuilder.append(
-                        getString(R.string.uninstall_application_text_user, userInfo.name));
+                if (userInfo.isManagedProfile()
+                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_current_user_work_profile,
+                                    userInfo.name));
+                } else {
+                    messageBuilder.append(
+                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                }
             } else {
                 messageBuilder.append(getString(R.string.uninstall_application_text));
             }
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index ee85c7b..00342082 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -40,6 +40,7 @@
 
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application
         android:allowClearUserData="true"
diff --git a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
index 36c2bda..7f17d26 100644
--- a/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
+++ b/packages/SettingsLib/ActivityEmbedding/src/com/android/settingslib/activityembedding/ActivityEmbeddingUtils.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ResolveInfo;
 
 import com.android.settingslib.utils.BuildCompatUtils;
 
@@ -37,11 +36,10 @@
         if (BuildCompatUtils.isAtLeastS()) {
             final Intent intent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
             intent.setPackage(PACKAGE_NAME_SETTINGS);
-            final ResolveInfo resolveInfo =
-                    context.getPackageManager().resolveActivity(intent, 0 /* flags */);
-            return resolveInfo != null
-                    && resolveInfo.activityInfo != null
-                    && resolveInfo.activityInfo.enabled;
+            final boolean isEmbeddingActivityEnabled =
+                    intent.resolveActivity(context.getPackageManager()) != null;
+
+            return isEmbeddingActivityEnabled;
         }
         return false;
     }
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
index 2272a37..2624a41 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
@@ -21,10 +21,10 @@
     <dimen name="settingslib_switchbar_margin">16dp</dimen>
 
     <!-- Size of layout margin left -->
-    <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
+    <dimen name="settingslib_switchbar_padding_left">20dp</dimen>
 
     <!-- Size of layout margin right -->
-    <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
+    <dimen name="settingslib_switchbar_padding_right">20dp</dimen>
 
     <!-- Minimum width of switch -->
     <dimen name="settingslib_min_switch_width">52dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 6362882..157a54e 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -24,7 +24,7 @@
     <dimen name="settingslib_restricted_icon_margin_end">16dp</dimen>
 
     <!-- Size of title margin -->
-    <dimen name="settingslib_switch_title_margin">16dp</dimen>
+    <dimen name="settingslib_switch_title_margin">24dp</dimen>
 
     <!-- SwitchBar sub settings margin start / end -->
     <dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index cb858c8..6d5615d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -109,7 +109,7 @@
             a.recycle();
         }
 
-        setBackground(true);
+        setBackground(mSwitch.isChecked());
     }
 
     @Override
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index c0623ed..ef548b5 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -7,6 +7,12 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "SettingsLibRestrictedLockUtilsSrc",
+    srcs: ["src/**/*.java"],
+    visibility: ["//frameworks/base/services/accessibility"],
+}
+
 android_library {
     name: "SettingsLibRestrictedLockUtils",
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index ff00fb3..0f34023 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -26,6 +26,7 @@
 import android.system.StructUtsname;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
@@ -33,7 +34,9 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.os.BuildCompat;
 
 import java.io.BufferedReader;
 import java.io.FileReader;
@@ -179,10 +182,8 @@
             SubscriptionInfo subscriptionInfo) {
         String formattedNumber = null;
         if (subscriptionInfo != null) {
-            final TelephonyManager telephonyManager = context.getSystemService(
-                    TelephonyManager.class);
-            final String rawNumber = telephonyManager.createForSubscriptionId(
-                    subscriptionInfo.getSubscriptionId()).getLine1Number();
+            final String rawNumber = getRawPhoneNumber(
+                    context, subscriptionInfo.getSubscriptionId());
             if (!TextUtils.isEmpty(rawNumber)) {
                 formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
             }
@@ -194,12 +195,10 @@
             List<SubscriptionInfo> subscriptionInfoList) {
         StringBuilder sb = new StringBuilder();
         if (subscriptionInfoList != null) {
-            final TelephonyManager telephonyManager = context.getSystemService(
-                    TelephonyManager.class);
             final int count = subscriptionInfoList.size();
             for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
-                final String rawNumber = telephonyManager.createForSubscriptionId(
-                        subscriptionInfo.getSubscriptionId()).getLine1Number();
+                final String rawNumber = getRawPhoneNumber(
+                        context, subscriptionInfo.getSubscriptionId());
                 if (!TextUtils.isEmpty(rawNumber)) {
                     sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
                 }
@@ -219,4 +218,21 @@
         final String phoneNumber = getFormattedPhoneNumber(context, subscriptionInfo);
         return BidiFormatter.getInstance().unicodeWrap(phoneNumber, TextDirectionHeuristics.LTR);
     }
+
+    private static String getRawPhoneNumber(Context context, int subscriptionId) {
+        if (BuildCompat.isAtLeastT()) {
+            return getRawPhoneNumberFromT(context, subscriptionId);
+        } else {
+            final TelephonyManager telephonyManager = context.getSystemService(
+                    TelephonyManager.class);
+            return telephonyManager.createForSubscriptionId(subscriptionId).getLine1Number();
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    private static String getRawPhoneNumberFromT(Context context, int subscriptionId) {
+        final SubscriptionManager subscriptionManager = context.getSystemService(
+                    SubscriptionManager.class);
+        return subscriptionManager.getPhoneNumber(subscriptionId);
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 3c78560..99e3160 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -28,13 +28,16 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
+import android.os.Build;
 import android.os.ParcelUuid;
 import android.util.Log;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 public class A2dpProfile implements LocalBluetoothProfile {
@@ -226,6 +229,10 @@
         return support == BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED;
     }
 
+    /**
+     * @return whether high quality audio is enabled or not
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public boolean isHighQualityAudioEnabled(BluetoothDevice device) {
         BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         if (bluetoothDevice == null) {
@@ -271,6 +278,13 @@
         }
     }
 
+    /**
+     * Gets the label associated with the codec of a Bluetooth device.
+     *
+     * @param device to get codec label from
+     * @return the label associated with the device codec
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
     public String getHighQualityAudioOptionLabel(BluetoothDevice device) {
         BluetoothDevice bluetoothDevice = (device != null) ? device : mService.getActiveDevice();
         int unknownCodecId = R.string.bluetooth_profile_a2dp_high_quality_unknown_codec;
@@ -280,18 +294,18 @@
         }
         // We want to get the highest priority codec, since that's the one that will be used with
         // this device, and see if it is high-quality (ie non-mandatory).
-        BluetoothCodecConfig[] selectable = null;
+        List<BluetoothCodecConfig> selectable = null;
         if (mService.getCodecStatus(device) != null) {
             selectable = mService.getCodecStatus(device).getCodecsSelectableCapabilities();
             // To get the highest priority, we sort in reverse.
-            Arrays.sort(selectable,
+            Collections.sort(selectable,
                     (a, b) -> {
                         return b.getCodecPriority() - a.getCodecPriority();
                     });
         }
 
-        final BluetoothCodecConfig codecConfig = (selectable == null || selectable.length < 1)
-                ? null : selectable[0];
+        final BluetoothCodecConfig codecConfig = (selectable == null || selectable.size() < 1)
+                ? null : selectable.get(0);
         final int codecType = (codecConfig == null || codecConfig.isMandatoryCodec())
                 ? BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID : codecConfig.getCodecType();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 389892e..8ac4e38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -170,6 +170,12 @@
     }
 
     @VisibleForTesting
+    void registerIntentReceiver() {
+        mContext.registerReceiverAsUser(mBroadcastReceiver, mUserHandle, mAdapterIntentFilter,
+                null, mReceiverHandler);
+    }
+
+    @VisibleForTesting
     void addProfileHandler(String action, Handler handler) {
         mHandlerMap.put(action, handler);
         mProfileIntentFilter.addAction(action);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index b429fe6..3debd9a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -87,7 +87,7 @@
             }
             // Check the member devices for the coordinated set if it exists
             final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
-            if (memberDevices != null) {
+            if (!memberDevices.isEmpty()) {
                 for (CachedBluetoothDevice memberDevice : memberDevices) {
                     if (memberDevice.getDevice().equals(device)) {
                         return memberDevice;
@@ -141,7 +141,7 @@
         final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
         // TODO: check the CSIP group size instead of the real member device set size, and adjust
         // the size restriction.
-        if (memberDevices.size() == 1) {
+        if (!memberDevices.isEmpty()) {
             for (CachedBluetoothDevice memberDevice : memberDevices) {
                 if (memberDevice.isConnected()) {
                     return memberDevice.getConnectionSummary();
@@ -166,7 +166,7 @@
             if (!cachedDevice.getDevice().equals(device)) {
                 // Check the member devices of the coordinated set if it exists
                 Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
-                if (memberDevices != null) {
+                if (!memberDevices.isEmpty()) {
                     for (CachedBluetoothDevice memberDevice : memberDevices) {
                         if (memberDevice.getDevice().equals(device)) {
                             return true;
@@ -230,9 +230,10 @@
     private void clearNonBondedSubDevices() {
         for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
-            final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
-            if (memberDevices != null) {
-                for (CachedBluetoothDevice memberDevice : memberDevices) {
+            Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
+            if (!memberDevices.isEmpty()) {
+                for (Object it : memberDevices.toArray()) {
+                    CachedBluetoothDevice memberDevice = (CachedBluetoothDevice) it;
                     // Member device exists and it is not bonded
                     if (memberDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
                         cachedDevice.removeMemberDevice(memberDevice);
@@ -257,7 +258,7 @@
             CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
             cachedDevice.setJustDiscovered(false);
             final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
-            if (memberDevices != null) {
+            if (!memberDevices.isEmpty()) {
                 for (CachedBluetoothDevice memberDevice : memberDevices) {
                     memberDevice.setJustDiscovered(false);
                 }
@@ -277,7 +278,7 @@
             for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
                 CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
                 final Set<CachedBluetoothDevice> memberDevices = cachedDevice.getMemberDevice();
-                if (memberDevices != null) {
+                if (!memberDevices.isEmpty()) {
                     for (CachedBluetoothDevice memberDevice : memberDevices) {
                         if (memberDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                             cachedDevice.removeMemberDevice(memberDevice);
@@ -315,7 +316,7 @@
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
         final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
-        if (memberDevices != null) {
+        if (!memberDevices.isEmpty()) {
             // Main device is unpaired, to unpair the member device
             for (CachedBluetoothDevice memberDevice : memberDevices) {
                 memberDevice.unpair();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index ed12e83..cc56a21 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -193,7 +193,7 @@
                     return true;
                 }
                 final Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
-                if (memberSet == null) {
+                if (memberSet.isEmpty()) {
                     break;
                 }
 
@@ -225,7 +225,7 @@
         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
             if (isValidGroupId(cachedDevice.getGroupId())) {
                 Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice();
-                if (memberSet == null) {
+                if (memberSet.isEmpty()) {
                     continue;
                 }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 1d8f71e..78ec58b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -18,11 +18,13 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.icu.text.ListFormatter;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.text.TextUtils;
@@ -155,9 +157,41 @@
         return set;
     }
 
-    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
+    /**
+     * Save the enabled/disabled input methods and selected subtype states into system settings.
+     *
+     * @param fragment The preference fragment user interact with.
+     * @param resolver The {@link ContentResolver} used to access the database.
+     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+     */
+    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat fragment,
             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
             boolean hasHardKeyboard) {
+        saveInputMethodSubtypeListForUserInternal(
+                fragment, resolver, inputMethodInfos, hasHardKeyboard, UserHandle.myUserId());
+    }
+
+    /**
+     * Save the enabled/disabled input methods and selected subtype states into system settings as
+     * given userId.
+     *
+     * @param fragment The preference fragment user interact with.
+     * @param resolver The {@link ContentResolver} used to access the database.
+     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
+     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
+     * @param userId The given userId
+     */
+    public static void saveInputMethodSubtypeListForUser(PreferenceFragmentCompat fragment,
+            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+            boolean hasHardKeyboard, @UserIdInt int userId) {
+        saveInputMethodSubtypeListForUserInternal(
+                fragment, resolver, inputMethodInfos, hasHardKeyboard, userId);
+    }
+
+    private static void saveInputMethodSubtypeListForUserInternal(PreferenceFragmentCompat fragment,
+            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+            boolean hasHardKeyboard, @UserIdInt int userId) {
         String currentInputMethodId = Settings.Secure.getString(resolver,
                 Settings.Secure.DEFAULT_INPUT_METHOD);
         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
@@ -168,7 +202,7 @@
         boolean needsToResetSelectedSubtype = false;
         for (final InputMethodInfo imi : inputMethodInfos) {
             final String imiId = imi.getId();
-            final Preference pref = context.findPreference(imiId);
+            final Preference pref = fragment.findPreference(imiId);
             if (pref == null) {
                 continue;
             }
@@ -184,8 +218,11 @@
             }
             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
             final boolean systemIme = imi.isSystem();
+            // Create context as given userId
+            final Context wrapperContext = userId == UserHandle.myUserId() ? fragment.getActivity()
+                    : fragment.getActivity().createContextAsUser(UserHandle.of(userId), 0);
             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
-                    context.getActivity()).isAlwaysCheckedIme(imi))
+                    wrapperContext).isAlwaysCheckedIme(imi))
                     || isImeChecked) {
                 if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
                     // imiId has just been enabled
@@ -198,7 +235,7 @@
                 for (int i = 0; i < subtypeCount; ++i) {
                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
-                    final TwoStatePreference subtypePref = (TwoStatePreference) context
+                    final TwoStatePreference subtypePref = (TwoStatePreference) fragment
                             .findPreference(imiId + subtypeHashCodeStr);
                     // In the Configure input method screen which does not have subtype preferences.
                     if (subtypePref == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 94a0c00..c1ab706 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -18,6 +18,7 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
+import android.annotation.UserIdInt;
 import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
@@ -75,30 +76,34 @@
     private final OnSavePreferenceListener mOnSaveListener;
     private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
     private final boolean mIsAllowedByOrganization;
+    @UserIdInt
+    private final int mUserId;
 
     private AlertDialog mDialog = null;
 
     /**
      * A preference entry of an input method.
      *
-     * @param context The Context this is associated with.
+     * @param prefContext The Context this preference is associated with.
      * @param imi The {@link InputMethodInfo} of this preference.
      * @param isAllowedByOrganization false if the IME has been disabled by a device or profile
      *     owner.
      * @param onSaveListener The listener called when this preference has been changed and needs
      *     to save the state to shared preference.
+     * @param userId The userId to specify the corresponding user for this preference.
      */
-    public InputMethodPreference(final Context context, final InputMethodInfo imi,
-            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener) {
-        this(context, imi, imi.loadLabel(context.getPackageManager()), isAllowedByOrganization,
-                onSaveListener);
+    public InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
+            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener,
+            final @UserIdInt int userId) {
+        this(prefContext, imi, imi.loadLabel(prefContext.getPackageManager()),
+                isAllowedByOrganization, onSaveListener, userId);
     }
 
     @VisibleForTesting
-    InputMethodPreference(final Context context, final InputMethodInfo imi,
+    InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
             final CharSequence title, final boolean isAllowedByOrganization,
-            final OnSavePreferenceListener onSaveListener) {
-        super(context);
+            final OnSavePreferenceListener onSaveListener, final @UserIdInt int userId) {
+        super(prefContext);
         setPersistent(false);
         mImi = imi;
         mIsAllowedByOrganization = isAllowedByOrganization;
@@ -114,7 +119,12 @@
             intent.setClassName(imi.getPackageName(), settingsActivity);
             setIntent(intent);
         }
-        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
+        // Handle the context by given userId because {@link InputMethodSettingValuesWrapper} is
+        // per-user instance.
+        final Context userAwareContext = userId == UserHandle.myUserId() ? prefContext :
+                getContext().createContextAsUser(UserHandle.of(userId), 0);
+        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(userAwareContext);
+        mUserId = userId;
         mHasPriorityInSorting = imi.isSystem()
                 && InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
         setOnPreferenceClickListener(this);
@@ -130,17 +140,15 @@
         super.onBindViewHolder(holder);
         final Switch switchWidget = getSwitch();
         if (switchWidget != null) {
+            // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
             switchWidget.setOnClickListener(v -> {
-                // no-op, avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}
-            });
-            switchWidget.setOnCheckedChangeListener((buttonView, isChecked) -> {
-                // Avoid the invocation after we call {@link PrimarySwitchPreference#setChecked()}
-                // in {@link setCheckedInternal}
-                if (isChecked != isChecked()) {
-                    // Keep switch to previous state because we have to show the dialog first
-                    buttonView.setChecked(!isChecked);
-                    callChangeListener(isChecked());
+                if (!switchWidget.isEnabled()) {
+                    return;
                 }
+                final boolean newValue = !isChecked();
+                // Keep switch to previous state because we have to show the dialog first.
+                switchWidget.setChecked(isChecked());
+                callChangeListener(newValue);
             });
         }
         final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
@@ -187,7 +195,7 @@
             final Intent intent = getIntent();
             if (intent != null) {
                 // Invoke a settings activity of an input method.
-                context.startActivity(intent);
+                context.startActivityAsUser(intent, UserHandle.of(mUserId));
             }
         } catch (final ActivityNotFoundException e) {
             Log.d(TAG, "IME's Settings Activity Not Found", e);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 13c1b82..39e6dce 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -16,13 +16,18 @@
 
 package com.android.settingslib.inputmethod;
 
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -39,20 +44,39 @@
 public class InputMethodSettingValuesWrapper {
     private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
 
-    private static volatile InputMethodSettingValuesWrapper sInstance;
+    private static final Object sInstanceMapLock = new Object();
+    /**
+     * Manages mapping between user ID and corresponding singleton
+     * {@link InputMethodSettingValuesWrapper} object.
+     */
+    @GuardedBy("sInstanceMapLock")
+    private static SparseArray<InputMethodSettingValuesWrapper> sInstanceMap = new SparseArray<>();
     private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     private final ContentResolver mContentResolver;
     private final InputMethodManager mImm;
 
-    public static InputMethodSettingValuesWrapper getInstance(Context context) {
-        if (sInstance == null) {
-            synchronized (TAG) {
-                if (sInstance == null) {
-                    sInstance = new InputMethodSettingValuesWrapper(context);
-                }
+    @AnyThread
+    @NonNull
+    public static InputMethodSettingValuesWrapper getInstance(@NonNull Context context) {
+        final int requestUserId = context.getUserId();
+        InputMethodSettingValuesWrapper valuesWrapper;
+        // First time to create the wrapper.
+        synchronized (sInstanceMapLock) {
+            if (sInstanceMap.size() == 0) {
+                valuesWrapper = new InputMethodSettingValuesWrapper(context);
+                sInstanceMap.put(requestUserId, valuesWrapper);
+                return valuesWrapper;
             }
+            // We have same user context as request.
+            if (sInstanceMap.indexOfKey(requestUserId) >= 0) {
+                return sInstanceMap.get(requestUserId);
+            }
+            // Request by a new user context.
+            valuesWrapper = new InputMethodSettingValuesWrapper(context);
+            sInstanceMap.put(context.getUserId(), valuesWrapper);
         }
-        return sInstance;
+
+        return valuesWrapper;
     }
 
     // Ensure singleton
@@ -64,7 +88,7 @@
 
     public void refreshAllInputMethodAndSubtypes() {
         mMethodList.clear();
-        mMethodList.addAll(mImm.getInputMethodList());
+        mMethodList.addAll(mImm.getInputMethodListAsUser(mContentResolver.getUserId()));
     }
 
     public List<InputMethodInfo> getInputMethodList() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index cd5c78d..360361b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -18,7 +18,6 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -57,12 +56,7 @@
 
     @Override
     public Drawable getIcon() {
-        final Drawable drawable =
-                BluetoothUtils.getBtDrawableWithDescription(mContext, mCachedDevice).first;
-        if (!(drawable instanceof BitmapDrawable)) {
-            setColorFilter(drawable);
-        }
-        return drawable;
+        return BluetoothUtils.getBtDrawableWithDescription(mContext, mCachedDevice).first;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index c34f65c..1b5ce8fe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -58,9 +58,7 @@
 
     @Override
     public Drawable getIcon() {
-        final Drawable drawable = getIconWithoutBackground();
-        setColorFilter(drawable);
-        return drawable;
+        return getIconWithoutBackground();
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index a49d7f6..970abff 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -15,6 +15,7 @@
  */
 package com.android.settingslib.media;
 
+import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
 import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
 import static android.media.MediaRoute2Info.TYPE_DOCK;
@@ -29,12 +30,8 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-import static android.media.MediaRoute2Info.TYPE_BLE_HEADSET;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -44,8 +41,6 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settingslib.R;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -141,14 +136,6 @@
                 getId());
     }
 
-    void setColorFilter(Drawable drawable) {
-        final ColorStateList list =
-                mContext.getResources().getColorStateList(
-                        R.color.advanced_icon_color, mContext.getTheme());
-        drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
-                PorterDuff.Mode.SRC_IN));
-    }
-
     /**
      * Get name from MediaDevice.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 1139d33..c16ecb5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -84,9 +84,7 @@
 
     @Override
     public Drawable getIcon() {
-        final Drawable drawable = getIconWithoutBackground();
-        setColorFilter(drawable);
-        return drawable;
+        return getIconWithoutBackground();
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 8b17be1..dee6894 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -85,6 +85,7 @@
     @VisibleForTesting
     protected Context mContext;
     private final int mThemeResId;
+    private final boolean mCancelIsNeutral;
     @VisibleForTesting
     protected TextView mZenAlarmWarning;
     @VisibleForTesting
@@ -101,8 +102,13 @@
     }
 
     public EnableZenModeDialog(Context context, int themeResId) {
+        this(context, themeResId, false /* cancelIsNeutral */);
+    }
+
+    public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) {
         mContext = context;
         mThemeResId = themeResId;
+        mCancelIsNeutral = cancelIsNeutral;
     }
 
     public AlertDialog createDialog() {
@@ -115,7 +121,6 @@
 
         final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
                 .setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
-                .setNegativeButton(R.string.cancel, null)
                 .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
                         new DialogInterface.OnClickListener() {
                             @Override
@@ -145,6 +150,12 @@
                             }
                         });
 
+        if (mCancelIsNeutral) {
+            builder.setNeutralButton(R.string.cancel, null);
+        } else {
+            builder.setNegativeButton(R.string.cancel, null);
+        }
+
         View contentView = getContentView();
         bindConditions(forever());
         builder.setView(contentView);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index bf0dc7b..1343895 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -261,8 +261,6 @@
     private void updateWifiState() {
         state = mWifiManager.getWifiState();
         enabled = state == WifiManager.WIFI_STATE_ENABLED;
-        isCarrierMerged = false;
-        subId = 0;
     }
 
     private void updateRssi(int newRssi) {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
index 9962e1c..1e75014 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -112,7 +113,8 @@
                 createInputMethodInfo(systemIme, name),
                 title,
                 true /* isAllowedByOrganization */,
-                p -> {} /* onSavePreferenceListener */);
+                p -> {} /* onSavePreferenceListener */,
+                UserHandle.myUserId());
     }
 
     private static InputMethodInfo createInputMethodInfo(
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 6d576a1..63153f8 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
@@ -159,7 +159,7 @@
         }
 
         public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
-                @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId) {
+                @PackageManager.ResolveInfoFlagsBits int flags, @UserIdInt int userId) {
             List<ResolveInfo> resolveInfos = new ArrayList<>();
             ResolveInfo resolveInfo = new ResolveInfo();
             resolveInfo.activityInfo = new ActivityInfo();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index 9afdd43c..f167721 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -43,6 +43,9 @@
 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 A2dpProfileTest {
@@ -179,7 +182,7 @@
                 BluetoothProfile.STATE_CONNECTED);
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
-        BluetoothCodecConfig[] configs = {config};
+        List<BluetoothCodecConfig> configs = Arrays.asList(config);
         when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
         when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
 
@@ -194,7 +197,7 @@
                 BluetoothProfile.STATE_CONNECTED);
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
-        BluetoothCodecConfig[] configs = {config};
+        List<BluetoothCodecConfig> configs = Arrays.asList(config);
         when(mBluetoothA2dp.getCodecStatus(mDevice)).thenReturn(status);
         when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 4bff78f..bee466d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -109,7 +109,7 @@
                         /* handler= */ null, /* userHandle= */ null);
 
         verify(mockContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
-                eq(null), eq(null));
+                eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
     }
 
     @Test
@@ -120,7 +120,7 @@
                         /* handler= */ null, UserHandle.ALL);
 
         verify(mockContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq(UserHandle.ALL),
-                any(IntentFilter.class), eq(null), eq(null));
+                any(IntentFilter.class), eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
     }
 
     /**
@@ -129,6 +129,7 @@
     @Test
     public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
 
         mContext.sendBroadcast(mIntent);
@@ -142,6 +143,7 @@
     @Test
     public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
 
         mContext.sendBroadcast(mIntent);
@@ -167,6 +169,7 @@
     @Test
     public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -179,6 +182,7 @@
     @Test
     public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -192,6 +196,7 @@
     public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() {
         when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -205,6 +210,7 @@
     public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() {
         when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -218,6 +224,7 @@
     public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() {
         when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
         mBluetoothEventManager.registerCallback(mBluetoothCallback);
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
 
@@ -354,6 +361,7 @@
 
     @Test
     public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -369,6 +377,7 @@
 
     @Test
     public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() {
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -385,6 +394,7 @@
 
     @Test
     public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() {
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -400,6 +410,7 @@
 
     @Test
     public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() {
+        mBluetoothEventManager.registerIntentReceiver();
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
         mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
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 2c4f57f..d53a3e8 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
@@ -26,7 +26,9 @@
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
 import android.content.Context;
+import android.os.ParcelUuid;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,6 +39,8 @@
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 
 @RunWith(RobolectricTestRunner.class)
 public class CachedBluetoothDeviceManagerTest {
@@ -51,6 +55,10 @@
     private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33";
     private final static long HISYNCID1 = 10;
     private final static long HISYNCID2 = 11;
+    private final static Map<Integer, ParcelUuid> CAP_GROUP1 =
+            Map.of(1, BluetoothUuid.CAP);
+    private final static Map<Integer, ParcelUuid> CAP_GROUP2 =
+            Map.of(2, BluetoothUuid.CAP);
     private final BluetoothClass DEVICE_CLASS_1 =
         new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
     private final BluetoothClass DEVICE_CLASS_2 =
@@ -70,6 +78,8 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private CsipSetCoordinatorProfile mCsipSetCoordinatorProfile;
+    @Mock
     private BluetoothDevice mDevice1;
     @Mock
     private BluetoothDevice mDevice2;
@@ -105,8 +115,12 @@
         when(mA2dpProfile.isProfileReady()).thenReturn(true);
         when(mPanProfile.isProfileReady()).thenReturn(true);
         when(mHearingAidProfile.isProfileReady()).thenReturn(true);
+        when(mCsipSetCoordinatorProfile.isProfileReady())
+                .thenReturn(true);
         doAnswer((invocation) -> mHearingAidProfile).
                 when(mLocalProfileManager).getHearingAidProfile();
+        doAnswer((invocation) -> mCsipSetCoordinatorProfile)
+                .when(mLocalProfileManager).getCsipSetCoordinatorProfile();
         mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
         mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
         mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
@@ -298,7 +312,7 @@
      * Test to verify OnDeviceUnpaired() for main hearing Aid device unpair.
      */
     @Test
-    public void onDeviceUnpaired_unpairMainDevice() {
+    public void onDeviceUnpaired_unpairHearingAidMainDevice() {
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
         CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
@@ -398,4 +412,153 @@
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
         assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isTrue();
     }
+
+     /**
+     * Test to verify getMemberDevice(), new device has the same group id.
+     */
+    @Test
+    public void addDevice_sameGroupId_validMemberDevice() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice3);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
+
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
+    }
+
+    /**
+     * Test to verify getMemberDevice(), new device has the different group id.
+     */
+    @Test
+    public void addDevice_differentGroupId_validMemberDevice() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+        assertThat(cachedDevice1.getMemberDevice()).isEmpty();
+    }
+
+    /**
+     * Test to verify addDevice(), new device has the same group id.
+     */
+    @Test
+    public void addDevice_sameGroupId_validCachedDevices_mainDevicesAdded_memberDevicesNotAdded() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).doesNotContain(cachedDevice2);
+    }
+
+    /**
+     * Test to verify addDevice(), new device has the different group id.
+     */
+    @Test
+    public void addDevice_differentGroupId_validCachedDevices_bothAdded() {
+        doAnswer((invocation) -> CAP_GROUP1).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice1);
+        doAnswer((invocation) -> CAP_GROUP2).when(mCsipSetCoordinatorProfile)
+                .getGroupUuidMapByDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).contains(cachedDevice2);
+    }
+
+    /**
+     * Test to verify clearNonBondedDevices() for csip set member device.
+     */
+    @Test
+    public void clearNonBondedDevices_nonBondedMemberDevice() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3);
+        cachedDevice1.setMemberDevice(cachedDevice2);
+        cachedDevice1.setMemberDevice(cachedDevice3);
+
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2);
+        assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3);
+        mCachedDeviceManager.clearNonBondedDevices();
+
+        assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice2)).isFalse();
+        assertThat(cachedDevice1.getMemberDevice().contains(cachedDevice3)).isTrue();
+    }
+
+    /**
+     * Test to verify OnDeviceUnpaired() for csip device unpair.
+     */
+    @Test
+    public void onDeviceUnpaired_unpairCsipMainDevice() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        cachedDevice1.setGroupId(1);
+        cachedDevice2.setGroupId(1);
+        cachedDevice1.setMemberDevice(cachedDevice2);
+
+        // Call onDeviceUnpaired for the one in mCachedDevices.
+        mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
+        verify(mDevice2).removeBond();
+    }
+
+    /**
+     * Test to verify OnDeviceUnpaired() for csip device unpair.
+     */
+    @Test
+    public void onDeviceUnpaired_unpairCsipSubDevice() {
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2);
+        cachedDevice1.setGroupId(1);
+        cachedDevice2.setGroupId(1);
+        cachedDevice1.setMemberDevice(cachedDevice2);
+
+        // Call onDeviceUnpaired for the one in mCachedDevices.
+        mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
+        verify(mDevice1).removeBond();
+    }
+
+    /**
+     * Test to verify isSubDevice_validSubDevice().
+     */
+    @Test
+    public void isSubDevice_validMemberDevice() {
+        doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice1);
+        mCachedDeviceManager.addDevice(mDevice1);
+
+        // Both device are not sub device in default value.
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isFalse();
+
+        // Add Device-2 as device with Device-1 with the same group id, and add Device-3 with
+        // the different group id.
+        doReturn(CAP_GROUP1).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice2);
+        doReturn(CAP_GROUP2).when(mCsipSetCoordinatorProfile).getGroupUuidMapByDevice(mDevice3);
+        mCachedDeviceManager.addDevice(mDevice2);
+        mCachedDeviceManager.addDevice(mDevice3);
+
+        // Verify Device-2 is sub device, but Device-1, and Device-3 is not.
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice1)).isFalse();
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice2)).isTrue();
+        assertThat(mCachedDeviceManager.isSubDevice(mDevice3)).isFalse();
+    }
 }
diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml
index 04d3f94..c5bea2e 100644
--- a/packages/SettingsProvider/AndroidManifest.xml
+++ b/packages/SettingsProvider/AndroidManifest.xml
@@ -3,6 +3,8 @@
         coreApp="true"
         android:sharedUserId="android.uid.system">
 
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
     <application android:allowClearUserData="false"
                  android:label="@string/app_label"
                  android:process="system"
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index cf960aa..7732da4 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -272,15 +272,9 @@
 
     <integer name="def_wearable_offChargerWifiUsageLimitMinutes">120</integer>
 
-    <!-- Default enabled state of accelerometer-based up/down gestures. -->
-    <bool name="def_wearable_upDownGesturesEnabled">false</bool>
-
     <!-- Whether to enable mute when off body by default. -->
     <bool name="def_wearable_muteWhenOffBodyEnabled">true</bool>
 
-    <!-- Whether to use an alternate launcher if available. -->
-    <bool name="def_wearable_alternateLauncherEnabled">true</bool>
-
     <!-- If a square screen, how rounded the corners are. Same as CSS border-radius property. -->
     <integer name="def_wearable_squareScreenCornerRoundness">0</integer>
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index a10b819..08e491d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -205,7 +205,6 @@
         VALIDATORS.put(
                 Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS,
                 ANY_INTEGER_VALIDATOR);
-        VALIDATORS.put(Global.Wearable.UPDOWN_GESTURES_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Global.Wearable.SETUP_SKIPPED,
                 new DiscreteValueValidator(
@@ -247,7 +246,6 @@
                             String.valueOf(Global.Wearable.STEM_TYPE_CONTACT_LAUNCH)
                         }));
         VALIDATORS.put(Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED, BOOLEAN_VALIDATOR);
-        VALIDATORS.put(Global.Wearable.ALTERNATE_LAUNCHER_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.CORNER_ROUNDNESS, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.Wearable.SIDE_BUTTON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.BUTTON_SET, 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 dd1cb6b..e76e51d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -276,6 +276,8 @@
         VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
+                NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(
                 Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a67b565..6072f68 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1067,14 +1067,17 @@
                 Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
                 GlobalSettingsProto.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE);
 
-        final long nitzUpdateToken = p.start(GlobalSettingsProto.NITZ_UPDATE);
+        final long nitzToken = p.start(GlobalSettingsProto.NITZ);
         dumpSetting(s, p,
                 Settings.Global.NITZ_UPDATE_DIFF,
-                GlobalSettingsProto.NitzUpdate.DIFF);
+                GlobalSettingsProto.Nitz.UPDATE_DIFF);
         dumpSetting(s, p,
                 Settings.Global.NITZ_UPDATE_SPACING,
-                GlobalSettingsProto.NitzUpdate.SPACING);
-        p.end(nitzUpdateToken);
+                GlobalSettingsProto.Nitz.UPDATE_SPACING);
+        dumpSetting(s, p,
+                Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
+                GlobalSettingsProto.Nitz.NETWORK_DISCONNECT_RETENTION);
+        p.end(nitzToken);
 
         final long notificationToken = p.start(GlobalSettingsProto.NOTIFICATION);
         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 11e4916..1e2d4e9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -25,6 +25,7 @@
 import static android.provider.Settings.SET_ALL_RESULT_DISABLED;
 import static android.provider.Settings.SET_ALL_RESULT_FAILURE;
 import static android.provider.Settings.SET_ALL_RESULT_SUCCESS;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
 import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
@@ -1346,6 +1347,13 @@
             // Anyone can get the global settings, so no security checks.
             for (int i = 0; i < nameCount; i++) {
                 String name = names.get(i);
+                try {
+                    enforceSettingReadable(name, SETTINGS_TYPE_GLOBAL,
+                            UserHandle.getCallingUserId());
+                } catch (SecurityException e) {
+                    // Caller doesn't have permission to read this setting
+                    continue;
+                }
                 Setting setting = settingsState.getSettingLocked(name);
                 appendSettingToCursor(result, setting);
             }
@@ -1523,6 +1531,13 @@
                     continue;
                 }
 
+                try {
+                    enforceSettingReadable(name, SETTINGS_TYPE_SECURE, callingUserId);
+                } catch (SecurityException e) {
+                    // Caller doesn't have permission to read this setting
+                    continue;
+                }
+
                 // As of Android O, the SSAID is read from an app-specific entry in table
                 // SETTINGS_FILE_SSAID, unless accessed by a system process.
                 final Setting setting;
@@ -1785,7 +1800,12 @@
 
             for (int i = 0; i < nameCount; i++) {
                 String name = names.get(i);
-
+                try {
+                    enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, callingUserId);
+                } catch (SecurityException e) {
+                    // Caller doesn't have permission to read this setting
+                    continue;
+                }
                 // Determine the owning user as some profile settings are cloned from the parent.
                 final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId,
                         name);
@@ -3604,7 +3624,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 207;
+            private static final int SETTINGS_VERSION = 208;
 
             private final int mUserId;
 
@@ -5242,11 +5262,6 @@
                     initGlobalSettingsDefaultValForWearLocked(
                             Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS, 0L);
                     initGlobalSettingsDefaultValForWearLocked(
-                            Global.Wearable.UPDOWN_GESTURES_ENABLED,
-                            getContext()
-                                    .getResources()
-                                    .getBoolean(R.bool.def_wearable_upDownGesturesEnabled));
-                    initGlobalSettingsDefaultValForWearLocked(
                             Global.Wearable.SETUP_SKIPPED, Global.Wearable.SETUP_SKIPPED_UNKNOWN);
                     initGlobalSettingsDefaultValForWearLocked(
                             Global.Wearable.LAST_CALL_FORWARD_ACTION,
@@ -5259,11 +5274,6 @@
                     initGlobalSettingsDefaultValForWearLocked(
                             Global.Wearable.WEAR_OS_VERSION_STRING, "");
                     initGlobalSettingsDefaultValForWearLocked(
-                            Global.Wearable.ALTERNATE_LAUNCHER_ENABLED,
-                            getContext()
-                                    .getResources()
-                                    .getBoolean(R.bool.def_wearable_alternateLauncherEnabled));
-                    initGlobalSettingsDefaultValForWearLocked(
                             Global.Wearable.CORNER_ROUNDNESS,
                             getContext()
                                     .getResources()
@@ -5463,6 +5473,30 @@
                     currentVersion = 207;
                 }
 
+                if (currentVersion == 207) {
+                    // Version 207: Reset the
+                    // Secure#ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT as enabled
+                    // status for showing the tooltips.
+                    final SettingsState secureSettings = getSecureSettingsLocked(userId);
+                    final Setting accessibilityButtonMode = secureSettings.getSettingLocked(
+                            Secure.ACCESSIBILITY_BUTTON_MODE);
+                    if (!accessibilityButtonMode.isNull()
+                            && accessibilityButtonMode.getValue().equals(
+                            String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU))) {
+                        if (isGestureNavigateEnabled()
+                                && hasValueInA11yButtonTargets(secureSettings)) {
+                            secureSettings.insertSettingLocked(
+                                    Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+                                    /* enabled */ "1",
+                                    /* tag= */ null,
+                                    /* makeDefault= */ false,
+                                    SettingsState.SYSTEM_PACKAGE_NAME);
+                        }
+                    }
+
+                    currentVersion = 208;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c9c93c4..0dfad17 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -384,6 +384,7 @@
                     Settings.Global.NETWORK_WATCHLIST_ENABLED,
                     Settings.Global.NEW_CONTACT_AGGREGATOR,
                     Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE,
+                    Settings.Global.NITZ_NETWORK_DISCONNECT_RETENTION,
                     Settings.Global.NITZ_UPDATE_DIFF,
                     Settings.Global.NITZ_UPDATE_SPACING,
                     Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
@@ -395,6 +396,7 @@
                     Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE,
                     Settings.Global.OVERLAY_DISPLAY_DEVICES,
                     Settings.Global.PAC_CHANGE_DELAY,
+                    Settings.Global.PACKAGE_STREAMING_VERIFIER_TIMEOUT,
                     Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
                     Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB,
                     Settings.Global.PACKAGE_VERIFIER_SETTING_VISIBLE,
@@ -610,7 +612,6 @@
                     Settings.Global.Wearable.AUTO_WIFI,
                     Settings.Global.Wearable.WIFI_POWER_SAVE,
                     Settings.Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS,
-                    Settings.Global.Wearable.UPDOWN_GESTURES_ENABLED,
                     Settings.Global.Wearable.SETUP_SKIPPED,
                     Settings.Global.Wearable.LAST_CALL_FORWARD_ACTION,
                     Settings.Global.Wearable.STEM_1_TYPE,
@@ -624,7 +625,6 @@
                     Settings.Global.Wearable.STEM_3_DEFAULT_DATA,
                     Settings.Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED,
                     Settings.Global.Wearable.WEAR_OS_VERSION_STRING,
-                    Settings.Global.Wearable.ALTERNATE_LAUNCHER_ENABLED,
                     Settings.Global.Wearable.CORNER_ROUNDNESS,
                     Settings.Global.Wearable.BUTTON_SET,
                     Settings.Global.Wearable.SIDE_BUTTON,
@@ -656,7 +656,8 @@
                     Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
                     Settings.Global.Wearable.WRIST_ORIENTATION_MODE,
                     Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE,
-                    Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY);
+                    Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY,
+                    Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 867ab3c..a3f07d8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -165,6 +165,7 @@
     <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
     <uses-permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE" />
     <uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" />
+    <uses-permission android:name="android.permission.PACKAGE_VERIFICATION_AGENT" />
     <uses-permission android:name="android.permission.MOVE_PACKAGE" />
     <uses-permission android:name="android.permission.KEEP_UNINSTALLED_PACKAGES" />
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" />
@@ -200,11 +201,13 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.CREATE_USERS" />
+    <uses-permission android:name="android.permission.QUERY_USERS" />
     <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
     <uses-permission android:name="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP" />
     <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
     <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
     <uses-permission android:name="android.permission.QUERY_ADMIN_POLICY" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES" />
     <uses-permission android:name="android.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS" />
     <uses-permission android:name="android.permission.CLEAR_FREEZE_PERIOD" />
     <uses-permission android:name="android.permission.MODIFY_QUIET_MODE" />
@@ -238,6 +241,7 @@
     <uses-permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
     <uses-permission android:name="android.permission.ACTIVITY_EMBEDDING" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.MANAGE_CLOUDSEARCH" />
     <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     <uses-permission android:name="android.permission.MANAGE_AUTO_FILL" />
     <uses-permission android:name="android.permission.MANAGE_CONTENT_CAPTURE" />
@@ -256,6 +260,7 @@
     <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.STATUS_BAR" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
     <!-- Permission needed to rename bugreport notifications (so they're not shown as Shell) -->
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
     <uses-permission android:name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" />
@@ -570,6 +575,9 @@
     <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
 
+    <!-- Permission required for CTS test - CallAudioInterceptionTest -->
+    <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
+
     <!-- Permission required for CTS test - CtsRotationResolverServiceDeviceTestCases -->
     <uses-permission android:name="android.permission.MANAGE_ROTATION_RESOLVER" />
 
@@ -599,6 +607,13 @@
     <!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
     <uses-permission android:name="android.permission.LOCK_DEVICE" />
 
+    <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+    <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+
+    <!-- Permission required for CTS test - CommunalManagerTest -->
+    <uses-permission android:name="android.permission.WRITE_COMMUNAL_STATE" />
+    <uses-permission android:name="android.permission.READ_COMMUNAL_STATE" />
+
     <application android:label="@string/app_label"
                 android:theme="@android:style/Theme.DeviceDefault.DayNight"
                 android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 2f117fc..559c31a 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -234,9 +234,36 @@
     plugins: ["dagger2-compiler"],
 }
 
+soong_config_module_type_import {
+    from: "frameworks/base/services/Android.bp",
+    module_types: ["system_optimized_java_defaults"],
+}
+
+system_optimized_java_defaults {
+    name: "SystemUI_app_defaults",
+    soong_config_variables: {
+        SYSTEM_OPTIMIZE_JAVA: {
+            optimize: {
+                enabled: true,
+                optimize: true,
+                shrink: true,
+                proguard_flags_files: ["proguard.flags"],
+            },
+            conditions_default: {
+                optimize: {
+                    proguard_flags_files: ["proguard.flags"],
+                },
+            },
+        },
+    },
+}
+
 android_app {
     name: "SystemUI",
-    defaults: ["platform_app_defaults"],
+    defaults: [
+        "platform_app_defaults",
+        "SystemUI_app_defaults",
+    ],
     static_libs: [
         "SystemUI-core",
     ],
@@ -247,10 +274,6 @@
     certificate: "platform",
     privileged: true,
 
-    optimize: {
-        proguard_flags_files: ["proguard.flags"],
-    },
-
     kotlincflags: ["-Xjvm-default=enable"],
 
     dxflags: ["--multi-dex"],
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index dde0a19..1e9a41e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -121,6 +121,7 @@
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.MONITOR_INPUT" />
+    <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
     <uses-permission android:name="android.permission.INPUT_CONSUMER" />
 
     <!-- DreamManager -->
@@ -217,8 +218,8 @@
 
     <!-- notifications & DND access -->
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATIONS" />
-    <uses-permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS" />
     <uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <!-- It's like, reality, but, you know, virtual -->
     <uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
@@ -375,6 +376,7 @@
         </receiver>
 
         <service android:name=".ImageWallpaper"
+                android:singleUser="true"
                 android:permission="android.permission.BIND_WALLPAPER"
                 android:exported="true" />
 
@@ -587,11 +589,7 @@
             android:theme="@style/Theme.SystemUI.Dialog.Alert"
             android:finishOnCloseSystemDialogs="true"
             android:excludeFromRecents="true"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="com.android.intent.action.REQUEST_SLICE_PERMISSION" />
-            </intent-filter>
-        </activity>
+            android:exported="true" />
 
         <!-- platform logo easter egg activity -->
         <activity
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index b271595..092758e 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -25,6 +25,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
+        },
+        {
+            "exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly"
         }
       ]
     },
@@ -100,6 +103,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
+        },
+        {
+            "exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly"
         }
       ]
     }
@@ -119,6 +125,9 @@
         },
         {
             "exclude-annotation": "android.platform.test.scenario.annotation.LargeScreenOnly"
+        },
+        {
+            "exclude-annotation": "android.platform.test.scenario.annotation.FoldableOnly"
         }
       ]
     }
diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml
deleted file mode 100644
index 620dd48..0000000
--- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml
+++ /dev/null
@@ -1,18 +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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:pathData="M 0, 0 C 0.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1" />
\ No newline at end of file
diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml
deleted file mode 100644
index a268abc..0000000
--- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml
+++ /dev/null
@@ -1,18 +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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" />
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 7020603..a0d335d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -21,6 +21,7 @@
 import android.app.PendingIntent
 import android.app.TaskInfo
 import android.graphics.Matrix
+import android.graphics.Path
 import android.graphics.Rect
 import android.graphics.RectF
 import android.os.Looper
@@ -34,6 +35,7 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
+import android.view.animation.Interpolator
 import android.view.animation.PathInterpolator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.policy.ScreenDecorationsUtils
@@ -45,16 +47,46 @@
  * A class that allows activities to be started in a seamless way from a view that is transforming
  * nicely into the starting window.
  */
-class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
+class ActivityLaunchAnimator(
+    private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS)
+) {
     companion object {
+        @JvmField
+        val TIMINGS = LaunchAnimator.Timings(
+            totalDuration = 500L,
+            contentBeforeFadeOutDelay = 0L,
+            contentBeforeFadeOutDuration = 150L,
+            contentAfterFadeInDelay = 150L,
+            contentAfterFadeInDuration = 183L
+        )
+
+        val INTERPOLATORS = LaunchAnimator.Interpolators(
+            positionInterpolator = Interpolators.EMPHASIZED,
+            positionXInterpolator = createPositionXInterpolator(),
+            contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
+            contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
+        )
+
+        /** Durations & interpolators for the navigation bar fading in & out. */
         private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
         private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
-        private const val ANIMATION_DELAY_NAV_FADE_IN =
-            LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
+        private val ANIMATION_DELAY_NAV_FADE_IN =
+            TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN
+
+        private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
+        private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
+
+        /** The time we wait before timing out the remote animation after starting the intent. */
         private const val LAUNCH_TIMEOUT = 1000L
 
-        private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
-        private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
+        private fun createPositionXInterpolator(): Interpolator {
+            val path = Path().apply {
+                moveTo(0f, 0f)
+                cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
+                cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
+            }
+            return PathInterpolator(path)
+        }
     }
 
     /**
@@ -107,8 +139,8 @@
         val animationAdapter = if (!hideKeyguardWithAnimation) {
             RemoteAnimationAdapter(
                 runner,
-                LaunchAnimator.ANIMATION_DURATION,
-                LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
+                TIMINGS.totalDuration,
+                TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
             )
         } else {
             null
@@ -436,7 +468,6 @@
                 .withAlpha(1f)
                 .withMatrix(matrix)
                 .withWindowCrop(windowCrop)
-                .withLayer(window.prefixOrderIndex)
                 .withCornerRadius(cornerRadius)
                 .withVisibility(true)
                 .build()
@@ -449,7 +480,7 @@
             state: LaunchAnimator.State,
             linearProgress: Float
         ) {
-            val fadeInProgress = LaunchAnimator.getProgress(linearProgress,
+            val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
                 ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
 
             val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
@@ -464,7 +495,7 @@
                     .withWindowCrop(windowCrop)
                     .withVisibility(true)
             } else {
-                val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0,
+                val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0,
                     ANIMATION_DURATION_NAV_FADE_OUT)
                 params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
             }
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 de82ebd..f7a7603 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -20,7 +20,6 @@
 import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.app.Dialog
-import android.content.Context
 import android.graphics.Color
 import android.graphics.Rect
 import android.os.Looper
@@ -28,10 +27,11 @@
 import android.util.Log
 import android.util.MathUtils
 import android.view.GhostView
+import android.view.SurfaceControl
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewTreeObserver.OnPreDrawListener
+import android.view.ViewRootImpl
 import android.view.WindowManager
 import android.widget.FrameLayout
 import kotlin.math.roundToInt
@@ -42,12 +42,20 @@
  * A class that allows dialogs to be started in a seamless way from a view that is transforming
  * nicely into the starting dialog.
  */
-class DialogLaunchAnimator(
-    private val context: Context,
-    private val launchAnimator: LaunchAnimator,
-    private val dreamManager: IDreamManager
+class DialogLaunchAnimator @JvmOverloads constructor(
+    private val dreamManager: IDreamManager,
+    private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+    private var isForTesting: Boolean = false
 ) {
     private companion object {
+        private val TIMINGS = ActivityLaunchAnimator.TIMINGS
+
+        // We use the same interpolator for X and Y axis to make sure the dialog does not move out
+        // of the screen bounds during the animation.
+        private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy(
+            positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
+        )
+
         private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
     }
 
@@ -96,14 +104,14 @@
         animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
 
         val animatedDialog = AnimatedDialog(
-                context,
                 launchAnimator,
                 dreamManager,
                 animateFrom,
                 onDialogDismissed = { openedDialogs.remove(it) },
                 dialog = dialog,
                 animateBackgroundBoundsChange,
-                animatedParent
+                animatedParent,
+                isForTesting
         )
 
         openedDialogs.add(animatedDialog)
@@ -157,7 +165,6 @@
 }
 
 private class AnimatedDialog(
-    private val context: Context,
     private val launchAnimator: LaunchAnimator,
     private val dreamManager: IDreamManager,
 
@@ -174,10 +181,16 @@
     val dialog: Dialog,
 
     /** Whether we should animate the dialog background when its bounds change. */
-    private val animateBackgroundBoundsChange: Boolean,
+    animateBackgroundBoundsChange: Boolean,
 
     /** Launch animation corresponding to the parent [AnimatedDialog]. */
-    private val parentAnimatedDialog: AnimatedDialog? = null
+    private val parentAnimatedDialog: AnimatedDialog? = null,
+
+    /**
+     * Whether we are currently running in a test, in which case we need to disable
+     * synchronization.
+     */
+    private val isForTesting: Boolean
 ) {
     /**
      * The DecorView of this dialog window.
@@ -266,14 +279,14 @@
             // and the view that we added so that we can dismiss the dialog when this view is
             // clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
             // can't set the click listener directly on the (now fullscreen) DecorView.
-            val fullscreenTransparentBackground = FrameLayout(context)
+            val fullscreenTransparentBackground = FrameLayout(dialog.context)
             decorView.addView(
                 fullscreenTransparentBackground,
                 0 /* index */,
                 FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
             )
 
-            val dialogContentWithBackground = FrameLayout(context)
+            val dialogContentWithBackground = FrameLayout(dialog.context)
             dialogContentWithBackground.background = decorView.background
 
             // Make the window background transparent. Note that setting the window (or DecorView)
@@ -286,6 +299,13 @@
             fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
             dialogContentWithBackground.isClickable = true
 
+            // Make sure the transparent and dialog backgrounds are not focusable by accessibility
+            // features.
+            fullscreenTransparentBackground.importantForAccessibility =
+                View.IMPORTANT_FOR_ACCESSIBILITY_NO
+            dialogContentWithBackground.importantForAccessibility =
+                View.IMPORTANT_FOR_ACCESSIBILITY_NO
+
             fullscreenTransparentBackground.addView(
                 dialogContentWithBackground,
                 FrameLayout.LayoutParams(
@@ -329,8 +349,10 @@
                 ?.color
                 ?.defaultColor ?: Color.BLACK
 
-        // Make the background view invisible until we start the animation.
-        dialogContentWithBackground.visibility = View.INVISIBLE
+        // Make the background view invisible until we start the animation. We use the transition
+        // visibility like GhostView does so that we don't mess up with the accessibility tree (see
+        // b/204944038#comment17).
+        dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
 
         // Make sure the dialog is visible instantly and does not do any window animation.
         window.attributes.windowAnimations = R.style.Animation_LaunchAnimation
@@ -365,59 +387,77 @@
         // Show the dialog.
         dialog.show()
 
-        // Add a temporary touch surface ghost as soon as the window is ready to draw. This
-        // temporary ghost will be drawn together with the touch surface, but in the dialog
-        // window. Once it is drawn, we will make the touch surface invisible, and then start the
-        // animation. We do all this synchronization to avoid flicker that would occur if we made
-        // the touch surface invisible too early (before its ghost is drawn), leading to one or more
-        // frames with a hole instead of the touch surface (or its ghost).
-        decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
-            override fun onPreDraw(): Boolean {
-                decorView.viewTreeObserver.removeOnPreDrawListener(this)
-                addTemporaryTouchSurfaceGhost()
-                return true
-            }
-        })
-        decorView.invalidate()
+        addTouchSurfaceGhost()
     }
 
-    private fun addTemporaryTouchSurfaceGhost() {
+    private fun addTouchSurfaceGhost() {
+        if (decorView.viewRootImpl == null) {
+            // Make sure that we have access to the dialog view root to synchronize the creation of
+            // the ghost.
+            decorView.post(::addTouchSurfaceGhost)
+            return
+        }
+
         // Create a ghost of the touch surface (which will make the touch surface invisible) and add
-        // it to the dialog. We will wait for this ghost to be drawn before starting the animation.
-        val ghost = GhostView.addGhost(touchSurface, decorView)
-
-        // The ghost of the touch surface was just created, so the touch surface was made invisible.
-        // We make it visible again until the ghost is actually drawn.
-        touchSurface.visibility = View.VISIBLE
-
-        // Wait for the ghost to be drawn before continuing.
-        ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
-            override fun onPreDraw(): Boolean {
-                ghost.viewTreeObserver.removeOnPreDrawListener(this)
-                onTouchSurfaceGhostDrawn()
-                return true
-            }
+        // it to the host dialog. We trigger a one off synchronization to make sure that this is
+        // done in sync between the two different windows.
+        synchronizeNextDraw(then = {
+            isTouchSurfaceGhostDrawn = true
+            maybeStartLaunchAnimation()
         })
-        ghost.invalidate()
+        GhostView.addGhost(touchSurface, decorView)
+
+        // The ghost of the touch surface was just created, so the touch surface is currently
+        // invisible. We need to make sure that it stays invisible as long as the dialog is shown or
+        // animating.
+        (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
     }
 
-    private fun onTouchSurfaceGhostDrawn() {
-        // Make the touch surface invisible and make sure that it stays invisible as long as the
-        // dialog is shown or animating.
-        touchSurface.visibility = View.INVISIBLE
-        (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+    /**
+     * Synchronize the next draw of the touch surface and dialog view roots so that they are
+     * performed at the same time, in the same transaction. This is necessary to make sure that the
+     * ghost of the touch surface is drawn at the same time as the touch surface is made invisible
+     * (or inversely, removed from the UI when the touch surface is made visible).
+     */
+    private fun synchronizeNextDraw(then: () -> Unit) {
+        if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
+            !decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
+            // No need to synchronize if either the touch surface or dialog view is not attached
+            // to a window.
+            then()
+            return
+        }
 
-        // Add a pre draw listener to (maybe) start the animation once the touch surface is
-        // actually invisible.
-        touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
-            override fun onPreDraw(): Boolean {
-                touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
-                isTouchSurfaceGhostDrawn = true
-                maybeStartLaunchAnimation()
-                return true
+        // Consume the next frames of both view roots to make sure the ghost view is drawn at
+        // exactly the same time as when the touch surface is made invisible.
+        var remainingTransactions = 0
+        val mergedTransactions = SurfaceControl.Transaction()
+
+        fun onTransaction(transaction: SurfaceControl.Transaction?) {
+            remainingTransactions--
+            transaction?.let { mergedTransactions.merge(it) }
+
+            if (remainingTransactions == 0) {
+                mergedTransactions.apply()
+                then()
             }
-        })
-        touchSurface.invalidate()
+        }
+
+        fun consumeNextDraw(viewRootImpl: ViewRootImpl) {
+            if (viewRootImpl.consumeNextDraw(::onTransaction)) {
+                remainingTransactions++
+
+                // Make sure we trigger a traversal.
+                viewRootImpl.view.invalidate()
+            }
+        }
+
+        consumeNextDraw(touchSurface.viewRootImpl)
+        consumeNextDraw(decorView.viewRootImpl)
+
+        if (remainingTransactions == 0) {
+            then()
+        }
     }
 
     private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -483,7 +523,7 @@
 
     private fun onDialogDismissed() {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            context.mainExecutor.execute { onDialogDismissed() }
+            dialog.context.mainExecutor.execute { onDialogDismissed() }
             return
         }
 
@@ -556,25 +596,12 @@
                         .removeOnLayoutChangeListener(backgroundLayoutListener)
                 }
 
-                // The animated ghost was just removed. We create a temporary ghost that will be
-                // removed only once we draw the touch surface, to avoid flickering that would
-                // happen when removing the ghost too early (before the touch surface is drawn).
-                GhostView.addGhost(touchSurface, decorView)
-
-                touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
-                    override fun onPreDraw(): Boolean {
-                        touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
-
-                        // Now that the touch surface was drawn, we can remove the temporary ghost
-                        // and instantly dismiss the dialog.
-                        GhostView.removeGhost(touchSurface)
-                        onAnimationFinished(true /* instantDismiss */)
-                        onDialogDismissed(this@AnimatedDialog)
-
-                        return true
-                    }
+                // Make sure that the removal of the ghost and making the touch surface visible is
+                // done at the same time.
+                synchronizeNextDraw(then = {
+                    onAnimationFinished(true /* instantDismiss */)
+                    onDialogDismissed(this@AnimatedDialog)
                 })
-                touchSurface.invalidate()
             }
         )
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 5fec4cc..1835842 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -190,7 +190,11 @@
                 // Making the ghost view invisible will make the ghosted view visible, so order is
                 // important here.
                 ghostView.visibility = View.INVISIBLE
-                ghostedView.visibility = View.INVISIBLE
+
+                // Make the ghosted view invisible again. We use the transition visibility like
+                // GhostView does so that we don't mess up with the accessibility tree (see
+                // b/204944038#comment17).
+                ghostedView.setTransitionVisibility(View.INVISIBLE)
                 backgroundView.visibility = View.INVISIBLE
             }
             return
@@ -261,6 +265,10 @@
 
         GhostView.removeGhost(ghostedView)
         launchContainerOverlay.remove(backgroundView)
+
+        // Make sure that the view is considered VISIBLE by accessibility by first making it
+        // INVISIBLE then VISIBLE (see b/204944038#comment17 for more info).
+        ghostedView.visibility = View.INVISIBLE
         ghostedView.visibility = View.VISIBLE
         ghostedView.invalidate()
     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 3bf6c5e..ebe96eb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -27,25 +27,19 @@
 import android.util.MathUtils
 import android.view.View
 import android.view.ViewGroup
-import android.view.animation.AnimationUtils
-import android.view.animation.PathInterpolator
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
 import kotlin.math.roundToInt
 
 private const val TAG = "LaunchAnimator"
 
 /** A base class to animate a window launch (activity or dialog) from a view . */
-class LaunchAnimator @JvmOverloads constructor(
-    context: Context,
-    private val isForTesting: Boolean = false
+class LaunchAnimator(
+    private val timings: Timings,
+    private val interpolators: Interpolators
 ) {
     companion object {
         internal const val DEBUG = false
-        const val ANIMATION_DURATION = 500L
-        private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
-        private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
-        private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
-
-        private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
         private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
 
         /**
@@ -53,23 +47,20 @@
          * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
          */
         @JvmStatic
-        fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
+        fun getProgress(
+            timings: Timings,
+            linearProgress: Float,
+            delay: Long,
+            duration: Long
+        ): Float {
             return MathUtils.constrain(
-                (linearProgress * ANIMATION_DURATION - delay) / duration,
+                (linearProgress * timings.totalDuration - delay) / duration,
                 0.0f,
                 1.0f
             )
         }
     }
 
-    /** The interpolator used for the width, height, Y position and corner radius. */
-    private val animationInterpolator = AnimationUtils.loadInterpolator(context,
-        R.interpolator.launch_animation_interpolator_y)
-
-    /** The interpolator used for the X position. */
-    private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
-        R.interpolator.launch_animation_interpolator_x)
-
     private val launchContainerLocation = IntArray(2)
     private val cornerRadii = FloatArray(8)
 
@@ -159,6 +150,45 @@
         fun cancel()
     }
 
+    /** The timings (durations and delays) used by this animator. */
+    class Timings(
+        /** The total duration of the animation. */
+        val totalDuration: Long,
+
+        /** The time to wait before fading out the expanding content. */
+        val contentBeforeFadeOutDelay: Long,
+
+        /** The duration of the expanding content fade out. */
+        val contentBeforeFadeOutDuration: Long,
+
+        /**
+         * The time to wait before fading in the expanded content (usually an activity or dialog
+         * window).
+         */
+        val contentAfterFadeInDelay: Long,
+
+        /** The duration of the expanded content fade in. */
+        val contentAfterFadeInDuration: Long
+    )
+
+    /** The interpolators used by this animator. */
+    data class Interpolators(
+        /** The interpolator used for the Y position, width, height and corner radius. */
+        val positionInterpolator: Interpolator,
+
+        /**
+         * The interpolator used for the X position. This can be different than
+         * [positionInterpolator] to create an arc-path during the animation.
+         */
+        val positionXInterpolator: Interpolator = positionInterpolator,
+
+        /** The interpolator used when fading out the expanding content. */
+        val contentBeforeFadeOutInterpolator: Interpolator,
+
+        /** The interpolator used when fading in the expanded content. */
+        val contentAfterFadeInInterpolator: Interpolator
+    )
+
     /**
      * Start a launch animation controlled by [controller] towards [endState]. An intermediary
      * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
@@ -221,8 +251,8 @@
 
         // Update state.
         val animator = ValueAnimator.ofFloat(0f, 1f)
-        animator.duration = if (isForTesting) 0 else ANIMATION_DURATION
-        animator.interpolator = Interpolators.LINEAR
+        animator.duration = timings.totalDuration
+        animator.interpolator = LINEAR
 
         val launchContainerOverlay = launchContainer.overlay
         var cancelled = false
@@ -260,8 +290,8 @@
             // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
             // reversed animation.
             val linearProgress = animation.animatedFraction
-            val progress = animationInterpolator.getInterpolation(linearProgress)
-            val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
+            val progress = interpolators.positionInterpolator.getInterpolation(linearProgress)
+            val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress)
 
             val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
             val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
@@ -278,7 +308,12 @@
 
             // The expanding view can/should be hidden once it is completely covered by the opening
             // window.
-            state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
+            state.visible = getProgress(
+                timings,
+                linearProgress,
+                timings.contentBeforeFadeOutDelay,
+                timings.contentBeforeFadeOutDuration
+            ) < 1
 
             applyStateToWindowBackgroundLayer(
                 windowBackgroundLayer,
@@ -337,14 +372,25 @@
 
         // We first fade in the background layer to hide the expanding view, then fade it out
         // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
-        val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
+        val fadeInProgress = getProgress(
+            timings,
+            linearProgress,
+            timings.contentBeforeFadeOutDelay,
+            timings.contentBeforeFadeOutDuration
+        )
         if (fadeInProgress < 1) {
-            val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress)
+            val alpha =
+                interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
             drawable.alpha = (alpha * 0xFF).roundToInt()
         } else {
             val fadeOutProgress = getProgress(
-                linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
-            val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
+                timings,
+                linearProgress,
+                timings.contentAfterFadeInDelay,
+                timings.contentAfterFadeInDuration
+            )
+            val alpha =
+                1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
             drawable.alpha = (alpha * 0xFF).roundToInt()
 
             if (drawHole) {
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index a16f5cd..da9a92a 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -20,9 +20,11 @@
 import android.app.smartspace.SmartspaceAction;
 import android.app.smartspace.SmartspaceTarget;
 import android.app.smartspace.SmartspaceTargetEvent;
+import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.os.Parcelable;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -39,6 +41,7 @@
 public interface BcSmartspaceDataPlugin extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
     int VERSION = 1;
+    String TAG = "BcSmartspaceDataPlugin";
 
     /** Register a listener to get Smartspace data. */
     void registerListener(SmartspaceTargetListener listener);
@@ -124,10 +127,14 @@
     /** Interface for launching Intents, which can differ on the lockscreen */
     interface IntentStarter {
         default void startFromAction(SmartspaceAction action, View v, boolean showOnLockscreen) {
-            if (action.getIntent() != null) {
-                startIntent(v, action.getIntent(), showOnLockscreen);
-            } else if (action.getPendingIntent() != null) {
-                startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+            try {
+                if (action.getIntent() != null) {
+                    startIntent(v, action.getIntent(), showOnLockscreen);
+                } else if (action.getPendingIntent() != null) {
+                    startPendingIntent(action.getPendingIntent(), showOnLockscreen);
+                }
+            } catch (ActivityNotFoundException e) {
+                Log.w(TAG, "Could not launch intent for action: " + action, e);
             }
         }
 
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 3517eba..ce63b44 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -33,10 +33,14 @@
     *;
 }
 
--keep class com.android.systemui.dagger.GlobalRootComponent { *; }
--keep class com.android.systemui.dagger.GlobalRootComponent$SysUIComponentImpl { *; }
--keep class com.android.systemui.dagger.Dagger** { *; }
--keep class com.android.systemui.tv.Dagger** { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.GlobalRootComponent { !synthetic *; }
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.GlobalRootComponent$SysUIComponentImpl { !synthetic *; }
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.Dagger** { !synthetic *; }
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.Dagger** { !synthetic *; }
+
+# Allows proguard to make private and protected methods and fields public as
+# part of optimization. This lets proguard inline trivial getter/setter methods.
+-allowaccessmodification
 
 # Removes runtime checks added through Kotlin to JVM code genereration to
 # avoid linear growth as more Kotlin code is converted / added to the codebase.
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_unlocked_aod.xml b/packages/SystemUI/res-keyguard/drawable/ic_unlocked_aod.xml
new file mode 100644
index 0000000..230a256
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_unlocked_aod.xml
@@ -0,0 +1,44 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+    <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125">
+        <path android:name="_R_G_L_2_G_D_0_P_0"
+              android:fillColor="#FF000000"
+              android:fillAlpha="1"
+              android:fillType="nonZero"
+              android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " />
+    </group>
+    <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125">
+        <path android:name="_R_G_L_1_G_D_0_P_0"
+              android:strokeColor="#FF000000"
+              android:strokeLineCap="round"
+              android:strokeLineJoin="round"
+              android:strokeWidth="1.5"
+              android:strokeAlpha="1"
+              android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " />
+    </group>
+    <group android:name="_R_G_L_0_G" android:translateX="14" android:translateY="13.5">
+        <path android:name="_R_G_L_0_G_D_0_P_0"
+              android:strokeColor="#FF000000"
+              android:strokeLineCap="round"
+              android:strokeLineJoin="round"
+              android:strokeWidth="1.5"
+              android:strokeAlpha="1"
+              android:pathData=" M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " />
+    </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml
new file mode 100644
index 0000000..177f695
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_header_bg.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+            android:paddingMode="stack"
+            android:paddingStart="44dp"
+            android:paddingEnd="44dp"
+            android:paddingLeft="0dp"
+            android:paddingRight="0dp">
+    <item>
+        <shape android:shape="rectangle">
+          <solid android:color="?androidprv:attr/colorSurface" />
+            <corners android:radius="@dimen/keyguard_user_switcher_corner" />
+        </shape>
+    </item>
+    <item
+        android:drawable="@drawable/ic_ksh_key_down"
+        android:gravity="end|center_vertical"
+        android:width="32dp"
+        android:height="32dp"
+        android:end="12dp" />
+</layer-list>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml
similarity index 74%
copy from libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
copy to packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml
index 22cd384..96a2d15 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_bubble.xml
+++ b/packages/SystemUI/res-keyguard/drawable/keyguard_user_switcher_popup_bg.xml
@@ -12,10 +12,11 @@
   ~ distributed under the License is distributed on an "AS IS" BASIS,
   ~ WITHOUT 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
   -->
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
        android:shape="rectangle">
-    <solid android:color="@color/size_compat_background"/>
-    <corners android:radius="@dimen/size_compat_hint_corner_radius"/>
-</shape>
\ No newline at end of file
+    <solid android:color="?androidprv:attr/colorSurface" />
+    <corners android:radius="@dimen/keyguard_user_switcher_popup_corner" />
+</shape>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index c58e2e3..b3987f1 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -50,6 +50,11 @@
         android:state_first="true"
         android:state_single="true"
         android:drawable="@drawable/ic_lock_aod" />
+    <item
+        android:id="@+id/unlocked_aod"
+        android:state_last="true"
+        android:state_single="true"
+        android:drawable="@drawable/ic_unlocked_aod" />
 
     <item
         android:id="@+id/no_icon"
@@ -79,4 +84,19 @@
         android:fromId="@id/locked"
         android:toId="@id/locked_aod"
         android:drawable="@drawable/lock_ls_to_aod" />
+
+    <transition
+        android:fromId="@id/unlocked_aod"
+        android:toId="@id/unlocked"
+        android:drawable="@drawable/unlocked_aod_to_ls" />
+
+    <transition
+        android:fromId="@id/unlocked"
+        android:toId="@id/unlocked_aod"
+        android:drawable="@drawable/unlocked_ls_to_aod" />
+
+    <transition
+        android:fromId="@id/unlocked"
+        android:toId="@id/locked_aod"
+        android:drawable="@drawable/unlocked_to_aod_lock" />
 </animated-selector>
diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_aod_to_ls.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_aod_to_ls.xml
new file mode 100644
index 0000000..3b59ba8
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlocked_aod_to_ls.xml
@@ -0,0 +1,133 @@
+<?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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+                 xmlns:android="http://schemas.android.com/apk/res/android">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_2_G_D_0_P_0"
+                          android:fillColor="#FF000000"
+                          android:fillAlpha="1"
+                          android:fillType="nonZero"
+                          android:pathData=" M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " />
+                </group>
+                <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_1_G_D_0_P_0"
+                          android:strokeColor="#FF000000"
+                          android:strokeLineCap="round"
+                          android:strokeLineJoin="round"
+                          android:strokeWidth="1.5"
+                          android:strokeAlpha="1"
+                          android:pathData=" M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " />
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="14" android:translateY="13.5">
+                    <path android:name="_R_G_L_0_G_D_0_P_0"
+                          android:strokeColor="#FF000000"
+                          android:strokeLineCap="round"
+                          android:strokeLineJoin="round"
+                          android:strokeWidth="1.5"
+                          android:strokeAlpha="1"
+                          android:pathData=" M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="333" android:startOffset="0" android:valueFrom="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueTo="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="1.5"
+                                android:valueTo="2"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.307,0 0.386,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueTo="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.372,0 0.203,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="1.5"
+                                android:valueTo="2.5"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " android:valueTo="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX"
+                                android:duration="517"
+                                android:startOffset="0"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_ls_to_aod.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_ls_to_aod.xml
new file mode 100644
index 0000000..1c6d0b5
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlocked_ls_to_aod.xml
@@ -0,0 +1,136 @@
+<?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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt"
+                 xmlns:android="http://schemas.android.com/apk/res/android">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_2_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_2_G_D_0_P_0"
+                          android:fillColor="#FF000000"
+                          android:fillAlpha="1"
+                          android:fillType="nonZero"
+                          android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " />
+                </group>
+                <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_1_G_D_0_P_0"
+                          android:strokeColor="#FF000000"
+                          android:strokeLineCap="round"
+                          android:strokeLineJoin="round"
+                          android:strokeWidth="2"
+                          android:strokeAlpha="1"
+                          android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " />
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="14" android:translateY="13.5">
+                    <path android:name="_R_G_L_0_G_D_0_P_0"
+                          android:strokeColor="#FF000000"
+                          android:strokeLineCap="round"
+                          android:strokeLineJoin="round"
+                          android:strokeWidth="2.5"
+                          android:strokeAlpha="1"
+                          android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="2"
+                                android:valueTo="1.5"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.516,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="2.5"
+                                android:valueTo="1.5"
+                                android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.516,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333"
+                                android:startOffset="0"
+                                android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M21.25 14.88 C21.25,14.88 21.25,10.74 21.25,10.74 C21.25,8.59 19.5,7.29 17.44,7.21 C15.24,7.13 13.5,8.47 13.5,10.62 C13.5,10.62 13.5,15.75 13.5,15.75 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.347,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX"
+                                android:duration="517"
+                                android:startOffset="0"
+                                android:valueFrom="0"
+                                android:valueTo="1"
+                                android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml
new file mode 100644
index 0000000..b6d76e0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/unlocked_to_aod_lock.xml
@@ -0,0 +1,169 @@
+<?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.
+-->
+<animated-vector xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+    <aapt:attr name="android:drawable">
+        <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_2_G_T_1" android:translateX="22.75" android:translateY="22.25" android:scaleX="1.02" android:scaleY="1.02">
+                    <group android:name="_R_G_L_2_G" android:translateX="-8.75" android:translateY="-8.75">
+                        <path android:name="_R_G_L_2_G_D_0_P_0" android:strokeColor="#FF000000"
+                              android:strokeLineJoin="round"
+                              android:strokeWidth="2.5"
+                              android:strokeAlpha="1"
+                              android:pathData=" M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " />
+                    </group>
+                </group>
+                <group android:name="_R_G_L_1_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_1_G_D_0_P_0"
+                          android:strokeColor="#FF000000"
+                          android:strokeLineJoin="round"
+                          android:strokeWidth="2"
+                          android:strokeAlpha="1"
+                          android:pathData=" M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " />
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="23" android:translateY="32.125">
+                    <path android:name="_R_G_L_0_G_D_0_P_0"
+                          android:fillColor="#FF000000"
+                          android:fillAlpha="1"
+                          android:fillType="nonZero"
+                          android:pathData=" M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333" android:startOffset="0" android:valueFrom="2.5" android:valueTo="2" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0.833,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="83" android:startOffset="0" android:valueFrom="M27.19 14.81 C27.19,14.81 27.19,8.3 27.19,8.3 C27.19,4.92 24.44,2.88 21.19,2.75 C17.74,2.62 15,4.74 15,8.11 C15,8.11 15,15 15,15 " android:valueTo="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.456,0 0.464,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="133" android:startOffset="83" android:valueFrom="M27.13 10.19 C27.13,10.19 27.13,3.67 27.13,3.67 C27.13,0.3 24.38,-1.75 21.13,-1.87 C17.68,-2.01 14.94,0.11 14.94,3.49 C14.94,3.49 15,15 15,15 " android:valueTo="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.606,0 0.035,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="117" android:startOffset="217" android:valueFrom="M2.5 10.38 C2.5,10.38 2.5,3.99 2.5,3.99 C2.5,0.61 5.3,-2.12 8.75,-2.12 C12.2,-2.12 15,0.61 15,3.99 C15,3.99 15,15 15,15 " android:valueTo="M2.5 15 C2.5,15 2.5,8.61 2.5,8.61 C2.5,5.24 5.3,2.5 8.75,2.5 C12.2,2.5 15,5.24 15,8.61 C15,8.61 15,15 15,15 " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.511,0 0.409,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX"
+                                android:duration="333" android:startOffset="0" android:valueFrom="22.75" android:valueTo="23" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateY" android:duration="333" android:startOffset="0" android:valueFrom="22.25" android:valueTo="25.5" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_T_1">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="scaleX"
+                                android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator android:propertyName="scaleY"
+                                android:duration="333" android:startOffset="0" android:valueFrom="1.02" android:valueTo="0.72" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="strokeWidth"
+                                android:duration="333" android:startOffset="0" android:valueFrom="2" android:valueTo="1.5" android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.38,0 0.274,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_1_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333" android:startOffset="0" android:valueFrom="M11.25 -0.64 C11.25,-0.64 11.25,13.64 11.25,13.64 C11.25,15.22 9.97,16.5 8.39,16.5 C8.39,16.5 -8.39,16.5 -8.39,16.5 C-9.97,16.5 -11.25,15.22 -11.25,13.64 C-11.25,13.64 -11.25,-0.64 -11.25,-0.64 C-11.25,-2.22 -9.97,-3.5 -8.39,-3.5 C-8.39,-3.5 8.39,-3.5 8.39,-3.5 C9.97,-3.5 11.25,-2.22 11.25,-0.64c " android:valueTo="M7.88 -0.62 C7.88,-0.62 7.88,9.38 7.88,9.38 C7.88,10.48 6.98,11.38 5.88,11.38 C5.88,11.38 -5.87,11.38 -5.87,11.38 C-6.98,11.38 -7.87,10.48 -7.87,9.38 C-7.87,9.38 -7.87,-0.62 -7.87,-0.62 C-7.87,-1.73 -6.98,-2.62 -5.87,-2.62 C-5.87,-2.62 5.88,-2.62 5.88,-2.62 C6.98,-2.62 7.88,-1.73 7.88,-0.62c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData"
+                                android:duration="333" android:startOffset="0" android:valueFrom="M-0.09 8.63 C1.2,8.63 2.25,7.57 2.25,6.28 C2.25,4.99 1.2,3.94 -0.09,3.94 C-1.39,3.94 -2.44,4.99 -2.44,6.28 C-2.44,7.57 -1.39,8.63 -0.09,8.63c " android:valueTo="M0 6.13 C0.97,6.13 1.75,5.34 1.75,4.38 C1.75,3.41 0.97,2.63 0,2.63 C-0.97,2.63 -1.75,3.41 -1.75,4.38 C-1.75,5.34 -0.97,6.13 0,6.13c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.431,0 0.133,1 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="translateX"
+                                android:duration="850" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
new file mode 100644
index 0000000..4f0925f
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    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
+                                                  from this view when bouncer is shown -->
+
+    <ImageView
+        android:id="@+id/user_icon"
+        android:layout_width="@dimen/keyguard_user_switcher_icon_size"
+        android:layout_height="@dimen/keyguard_user_switcher_icon_size" />
+
+    <!-- need to keep this outer view in order to have a correctly sized anchor
+         for the dropdown menu, as well as dropdown background in the right place -->
+    <LinearLayout
+        android:id="@+id/user_switcher_anchor"
+        android:orientation="horizontal"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="30dp"
+        android:minHeight="48dp">
+      <TextView
+          style="@style/Keyguard.UserSwitcher.Spinner.Header"
+          android:clickable="false"
+          android:id="@+id/user_switcher_header"
+          android:layout_width="@dimen/keyguard_user_switcher_width"
+          android:layout_height="wrap_content" />
+    </LinearLayout>>
+
+</LinearLayout>
+
diff --git a/packages/CompanionDeviceManager/res/layout/profile_summary.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
similarity index 66%
rename from packages/CompanionDeviceManager/res/layout/profile_summary.xml
rename to packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
index 80fec59..b08e1ff 100644
--- a/packages/CompanionDeviceManager/res/layout/profile_summary.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher_item.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2020 The Android Open Source Project
+  ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,17 +13,13 @@
   ~ 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:id="@+id/profile_summary"
-    android:layout_below="@+id/title"
+    style="@style/Keyguard.UserSwitcher.Spinner.Item"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginStart="16dp"
-    android:layout_marginEnd="16dp"
-    android:textColor="?android:attr/textColorSecondary"
-    android:textSize="14sp"
-    android:gravity="center"
-/>
\ No newline at end of file
+    android:layout_gravity="start"
+    android:paddingStart="@dimen/control_menu_horizontal_padding"
+    android:paddingEnd="@dimen/control_menu_horizontal_padding"
+    android:textDirection="locale"/>
+
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
index a946318..94566c7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view.xml
@@ -49,8 +49,6 @@
             android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
             androidprv:layout_constraintEnd_toEndOf="parent"
             androidprv:layout_constraintStart_toStartOf="parent"
-
-            androidprv:layout_constraintTop_toTopOf="parent"
             androidprv:layout_constraintBottom_toTopOf="@id/key1"
             androidprv:layout_constraintVertical_bias="0.0">
 
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
index 1863d11..7c5dbc2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_slice_view.xml
@@ -30,8 +30,6 @@
               android:id="@+id/title"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
-              android:paddingStart="44dp"
-              android:paddingEnd="44dp"
               android:visibility="gone"
               android:textColor="?attr/wallpaperTextColor"
               android:theme="@style/TextAppearance.Keyguard"
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
index e9bd638..e80cfaf 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
@@ -25,6 +25,9 @@
     <!-- Margin around the various security views -->
     <dimen name="keyguard_security_view_top_margin">12dp</dimen>
 
+    <!-- Padding for the lock icon on the keyguard -->
+    <dimen name="lock_icon_padding">16dp</dimen>
+
     <!-- Overload default clock widget parameters -->
     <dimen name="widget_big_font_size">100dp</dimen>
     <dimen name="widget_label_font_size">18sp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
index 4daa648..54bb1fc 100644
--- a/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
+++ b/packages/SystemUI/res-keyguard/values-sw720dp/bools.xml
@@ -18,7 +18,7 @@
 <resources>
     <!-- Allows PIN/Pattern to be drawn on one side of a display, and for the user to
          switch sides -->
-    <bool name="can_use_one_handed_bouncer">true</bool>
+    <bool name="can_use_one_handed_bouncer">false</bool>
 
     <!-- Will display the bouncer on one side of the display, and the current user icon and
          user switcher on the other side -->
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 89dd741..2819dc9 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -106,4 +106,13 @@
          opacity is zero), but this controls how much motion will actually be applied to it while
          animating. Larger values will cause it to move "faster" while fading out/in. -->
     <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
+
+
+    <dimen name="keyguard_user_switcher_header_text_size">32sp</dimen>
+    <dimen name="keyguard_user_switcher_item_text_size">32sp</dimen>
+    <dimen name="keyguard_user_switcher_width">320dp</dimen>
+    <dimen name="keyguard_user_switcher_icon_size">310dp</dimen>
+    <dimen name="keyguard_user_switcher_corner">32dp</dimen>
+    <dimen name="keyguard_user_switcher_popup_corner">24dp</dimen>
+    <dimen name="keyguard_user_switcher_item_padding_vertical">15dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 4fc38a8..1601043 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -219,6 +219,9 @@
     <!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
     <string name="kg_face_not_recognized">Not recognized</string>
 
+     <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
+    <string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
+
     <!-- Instructions telling the user remaining times when enter SIM PIN view.  -->
     <plurals name="kg_password_default_pin_message">
         <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index b0bdc72..60f034a 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -112,7 +112,7 @@
     <style name="TextAppearance.Keyguard">
         <item name="android:textSize">@dimen/widget_title_font_size</item>
         <item name="android:lineHeight">@dimen/widget_title_line_height</item>
-        <item name="android:gravity">center</item>
+        <item name="android:gravity">start</item>
         <item name="android:ellipsize">end</item>
         <item name="android:maxLines">2</item>
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
@@ -131,6 +131,7 @@
     <style name="TextAppearance.Keyguard.BottomArea">
         <item name="android:textSize">14sp</item>
         <item name="android:maxLines">1</item>
+        <item name="android:gravity">center</item>
         <item name="android:textColor">?attr/wallpaperTextColor</item>
         <item name="android:shadowColor">@color/keyguard_shadow_color</item>
         <item name="android:shadowRadius">?attr/shadowRadius</item>
@@ -139,4 +140,23 @@
     <style name="TextAppearance.Keyguard.BottomArea.Button">
         <item name="android:shadowRadius">0</item>
     </style>
+
+    <style name="Keyguard.UserSwitcher.Spinner" parent="@android:style/Widget.DeviceDefault.TextView">
+        <item name="android:textColor">?android:attr/textColorPrimary</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:paddingTop">@dimen/keyguard_user_switcher_item_padding_vertical</item>
+        <item name="android:paddingBottom">@dimen/keyguard_user_switcher_item_padding_vertical</item>
+    </style>
+
+    <style name="Keyguard.UserSwitcher.Spinner.Header">
+        <item name="android:background">@drawable/keyguard_user_switcher_header_bg</item>
+        <item name="android:textSize">@dimen/keyguard_user_switcher_header_text_size</item>
+    </style>
+
+    <style name="Keyguard.UserSwitcher.Spinner.Item">
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:textSize">@dimen/keyguard_user_switcher_item_text_size</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml b/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml
deleted file mode 100644
index 9a69b33..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_drag_handle.xml
+++ /dev/null
@@ -1,24 +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
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="36dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:fillColor="?android:attr/textColorPrimary"
-        android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" />
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
similarity index 62%
copy from libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
copy to packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
index af9063a..a93abb7 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_hint_point.xml
+++ b/packages/SystemUI/res/drawable/ic_signal_wifi_off.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
@@ -15,11 +14,11 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="@dimen/size_compat_hint_point_width"
-        android:height="8dp"
-        android:viewportWidth="10"
-        android:viewportHeight="8">
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
     <path
-        android:fillColor="@color/size_compat_background"
-        android:pathData="M10,0 l-4.1875,6.6875 a1,1 0 0,1 -1.625,0 l-4.1875,-6.6875z"/>
-</vector>
+        android:fillColor="@android:color/white"
+        android:pathData="M18.575,15.2L7.55,4.175q1.1,-0.325 2.212,-0.462 1.113,-0.138 2.238,-0.138 3.45,0 6.55,1.45Q21.65,6.475 24,9zM20.225,23.575l-4.75,-4.75 -3.475,4.1L0,9q0.675,-0.725 1.438,-1.4 0.762,-0.675 1.612,-1.225l-2.625,-2.6L2.1,2.1l19.8,19.8z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
deleted file mode 100644
index 50267fd..0000000
--- a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
+++ /dev/null
@@ -1,30 +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
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke
-        android:color="?androidprv:attr/colorAccentPrimaryVariant"
-        android:width="1dp"/>
-    <corners android:radius="20dp"/>
-    <padding
-        android:left="8dp"
-        android:right="8dp"
-        android:top="4dp"
-        android:bottom="4dp" />
-    <solid android:color="@android:color/transparent" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
deleted file mode 100644
index 363a022..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
+++ /dev/null
@@ -1,29 +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.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke
-        android:color="?androidprv:attr/colorAccentPrimaryVariant"
-        android:width="1dp"/>
-    <corners android:radius="20dp"/>
-    <padding
-        android:left="16dp"
-        android:right="16dp"
-        android:top="8dp"
-        android:bottom="8dp" />
-    <solid android:color="@android:color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
deleted file mode 100644
index 86f8b42..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
+++ /dev/null
@@ -1,29 +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.
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke
-        android:color="@android:color/transparent"
-        android:width="1dp"/>
-    <corners android:radius="20dp"/>
-    <padding
-        android:left="@dimen/media_output_dialog_button_padding_horizontal"
-        android:right="@dimen/media_output_dialog_button_padding_horizontal"
-        android:top="@dimen/media_output_dialog_button_padding_vertical"
-        android:bottom="@dimen/media_output_dialog_button_padding_vertical" />
-    <solid android:color="?androidprv:attr/colorAccentPrimaryVariant" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_item_background.xml b/packages/SystemUI/res/drawable/media_output_item_background.xml
index 8c23659..ea77f1b 100644
--- a/packages/SystemUI/res/drawable/media_output_item_background.xml
+++ b/packages/SystemUI/res/drawable/media_output_item_background.xml
@@ -15,9 +15,8 @@
   -->
 
 <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="16dp"/>
-    <solid android:color="?androidprv:attr/colorAccentSecondary" />
+    <solid android:color="@color/media_dialog_item_background" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_item_background_active.xml b/packages/SystemUI/res/drawable/media_output_item_background_active.xml
index 09dee95..839db4a 100644
--- a/packages/SystemUI/res/drawable/media_output_item_background_active.xml
+++ b/packages/SystemUI/res/drawable/media_output_item_background_active.xml
@@ -15,9 +15,8 @@
   -->
 
 <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="50dp"/>
-    <solid android:color="?androidprv:attr/colorAccentSecondary" />
+        android:radius="28dp"/>
+    <solid android:color="@color/media_dialog_item_background" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_status_check.xml b/packages/SystemUI/res/drawable/media_output_status_check.xml
index 4e17e48..1b750f8 100644
--- a/packages/SystemUI/res/drawable/media_output_status_check.xml
+++ b/packages/SystemUI/res/drawable/media_output_status_check.xml
@@ -15,13 +15,12 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         android:width="24dp"
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
         android:tint="?attr/colorControlNormal">
     <path
-        android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"
+        android:fillColor="@color/media_dialog_item_status"
         android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_failed.xml b/packages/SystemUI/res/drawable/media_output_status_failed.xml
index 81fb92c..05c6358 100644
--- a/packages/SystemUI/res/drawable/media_output_status_failed.xml
+++ b/packages/SystemUI/res/drawable/media_output_status_failed.xml
@@ -15,13 +15,12 @@
   -->
 
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         android:width="24dp"
         android:height="24dp"
         android:viewportWidth="24"
         android:viewportHeight="24"
         android:tint="?attr/colorControlNormal">
     <path
-        android:fillColor="?androidprv:attr/colorAccentPrimaryVariant"
+        android:fillColor="@color/media_dialog_inactive_item_main_content"
         android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
 </vector>
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index 665b456..a47299d 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -29,7 +29,7 @@
             <shape android:shape="rectangle">
                 <corners android:radius="?android:attr/buttonCornerRadius"/>
                 <solid android:color="@android:color/transparent"/>
-                <stroke android:color="?androidprv:attr/colorAccentPrimary"
+                <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
                         android:width="1dp"
                 />
                 <padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml b/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
deleted file mode 100644
index 59a31e8..0000000
--- a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
+++ /dev/null
@@ -1,30 +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
-  -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-       xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-       android:shape="rectangle">
-    <stroke
-        android:color="?androidprv:attr/colorAccentPrimary"
-        android:width="1dp"/>
-    <corners android:radius="24dp"/>
-    <padding
-        android:left="16dp"
-        android:right="16dp"
-        android:top="8dp"
-        android:bottom="8dp" />
-    <solid android:color="@android:color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 3c9e44e..89690e8 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -73,6 +73,9 @@
         android:accessibilityLiveRegion="polite"
         android:singleLine="true"
         android:ellipsize="marquee"
+        android:marqueeRepeatLimit="marquee_forever"
+        android:scrollHorizontally="true"
+        android:fadingEdge="horizontal"
         android:textColor="@color/biometric_dialog_gray"/>
 
     <LinearLayout
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 1f10e5d..405863d 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -101,4 +101,16 @@
         app:layout_constraintBottom_toBottomOf="parent"
     />
 
+    <FrameLayout
+        android:id="@+id/privacy_container"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:gravity="center"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="@id/date"
+        app:layout_constraintBottom_toBottomOf="@id/date"
+        >
+        <include layout="@layout/ongoing_privacy_chip"/>
+    </FrameLayout>
+
 </androidx.constraintlayout.motion.widget.MotionLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
new file mode 100644
index 0000000..b611ffa
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+-->
+<com.android.systemui.dreams.DreamOverlayContainerView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/dream_overlay_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/dream_overlay_content"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <com.android.systemui.dreams.DreamOverlayStatusBarView
+        android:id="@+id/dream_overlay_status_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dream_overlay_status_bar_height"
+        android:layout_marginEnd="@dimen/dream_overlay_status_bar_margin"
+        android:layout_marginStart="@dimen/dream_overlay_status_bar_margin"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/dream_overlay_system_status"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            app:layout_constraintEnd_toEndOf="parent">
+
+            <com.android.systemui.statusbar.AlphaOptimizedImageView
+                android:id="@+id/dream_overlay_wifi_status"
+                android:layout_width="@dimen/status_bar_wifi_signal_size"
+                android:layout_height="match_parent"
+                android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+                android:visibility="gone"
+                app:layout_constraintEnd_toStartOf="@id/dream_overlay_battery" />
+
+            <com.android.systemui.battery.BatteryMeterView
+                android:id="@+id/dream_overlay_battery"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                app:layout_constraintEnd_toEndOf="parent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.android.systemui.dreams.DreamOverlayStatusBarView>
+</com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index e90a644..baf5336 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -37,7 +37,7 @@
             android:ellipsize="end"
             android:gravity="center_vertical|center_horizontal"
             android:layout_width="wrap_content"
-            android:layout_height="32dp"
+            android:layout_height="wrap_content"
             android:textAppearance="@style/TextAppearance.InternetDialog"
             android:textSize="24sp"/>
 
@@ -45,7 +45,7 @@
             android:id="@+id/internet_dialog_subtitle"
             android:gravity="center_vertical|center_horizontal"
             android:layout_width="wrap_content"
-            android:layout_height="20dp"
+            android:layout_height="wrap_content"
             android:layout_marginTop="4dp"
             android:ellipsize="end"
             android:maxLines="1"
@@ -67,7 +67,6 @@
 
         <ProgressBar
             android:id="@+id/wifi_searching_progress"
-            android:indeterminate="true"
             android:layout_width="340dp"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
@@ -151,6 +150,7 @@
                         android:gravity="start|center_vertical">
                         <TextView
                             android:id="@+id/mobile_title"
+                            android:maxLines="1"
                             style="@style/InternetDialog.NetworkTitle"/>
                         <TextView
                             android:id="@+id/mobile_summary"
@@ -381,55 +381,43 @@
                 android:id="@+id/button_layout"
                 android:orientation="horizontal"
                 android:layout_width="match_parent"
-                android:layout_height="48dp"
-                android:layout_marginStart="24dp"
-                android:layout_marginEnd="24dp"
+                android:layout_height="wrap_content"
                 android:layout_marginTop="8dp"
-                android:layout_marginBottom="34dp"
+                android:layout_marginStart="@dimen/dialog_side_padding"
+                android:layout_marginEnd="@dimen/dialog_side_padding"
+                android:layout_marginBottom="@dimen/dialog_bottom_padding"
                 android:clickable="false"
                 android:focusable="false">
 
                 <FrameLayout
-                    android:id="@+id/apm_layout"
                     android:layout_width="wrap_content"
-                    android:layout_height="48dp"
-                    android:clickable="true"
-                    android:focusable="true"
+                    android:layout_height="wrap_content"
                     android:layout_gravity="start|center_vertical"
                     android:orientation="vertical">
                     <Button
+                        android:id="@+id/apm_button"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
                         android:text="@string/turn_off_airplane_mode"
                         android:ellipsize="end"
-                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                        android:layout_width="wrap_content"
-                        android:layout_height="36dp"
-                        android:layout_gravity="start|center_vertical"
-                        android:textAppearance="@style/TextAppearance.InternetDialog"
-                        android:textSize="14sp"
-                        android:background="@drawable/internet_dialog_footer_background"
-                        android:clickable="false"/>
+                        style="@style/Widget.Dialog.Button.BorderButton"
+                        android:clickable="true"
+                        android:focusable="true"/>
                 </FrameLayout>
 
                 <FrameLayout
-                    android:id="@+id/done_layout"
                     android:layout_width="wrap_content"
-                    android:layout_height="48dp"
+                    android:layout_height="wrap_content"
                     android:layout_marginStart="16dp"
-                    android:clickable="true"
-                    android:focusable="true"
-                    android:layout_gravity="end|center_vertical"
-                    android:orientation="vertical">
+                    android:layout_gravity="end|center_vertical">
                     <Button
+                        android:id="@+id/done_button"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
                         android:text="@string/inline_done_button"
-                        android:ellipsize="end"
-                        style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-                        android:layout_width="67dp"
-                        android:layout_height="36dp"
-                        android:layout_gravity="end|center_vertical"
-                        android:textAppearance="@style/TextAppearance.InternetDialog"
-                        android:textSize="14sp"
-                        android:background="@drawable/internet_dialog_footer_background"
-                        android:clickable="false"/>
+                        style="@style/Widget.Dialog.Button"
+                        android:clickable="true"
+                        android:focusable="true"/>
                 </FrameLayout>
             </FrameLayout>
         </LinearLayout>
diff --git a/packages/SystemUI/res/layout/long_screenshot.xml b/packages/SystemUI/res/layout/long_screenshot.xml
index 04bd7b9..856697c 100644
--- a/packages/SystemUI/res/layout/long_screenshot.xml
+++ b/packages/SystemUI/res/layout/long_screenshot.xml
@@ -31,7 +31,7 @@
         android:layout_height="40dp"
         android:text="@string/save"
         android:layout_marginStart="8dp"
-        android:layout_marginTop="4dp"
+        android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
         android:background="@drawable/screenshot_button_background"
         android:textColor="?android:textColorSecondary"
         app:layout_constraintStart_toStartOf="parent"
@@ -45,7 +45,7 @@
         android:layout_height="40dp"
         android:text="@android:string/cancel"
         android:layout_marginStart="6dp"
-        android:layout_marginTop="4dp"
+        android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
         android:background="@drawable/screenshot_button_background"
         android:textColor="?android:textColorSecondary"
         app:layout_constraintStart_toEndOf="@id/save"
@@ -60,7 +60,7 @@
         android:layout_width="48dp"
         android:layout_height="48dp"
         android:layout_marginEnd="8dp"
-        android:layout_marginTop="4dp"
+        android:layout_marginTop="@dimen/long_screenshot_action_bar_top_margin"
         android:padding="12dp"
         android:src="@drawable/ic_screenshot_share"
         android:scaleType="fitCenter"
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 4e2b4a6..b7265b9 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -66,7 +66,7 @@
                 android:ellipsize="end"
                 android:maxLines="1"
                 android:textColor="?android:attr/textColorSecondary"
-                android:fontFamily="roboto-regular"
+                android:fontFamily="@*android:string/config_bodyFontFamily"
                 android:textSize="14sp"/>
         </LinearLayout>
     </LinearLayout>
@@ -75,7 +75,6 @@
         android:id="@+id/device_list"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_weight="1"
         android:orientation="vertical">
 
         <androidx.recyclerview.widget.RecyclerView
@@ -90,17 +89,16 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginTop="4dp"
-        android:layout_marginStart="16dp"
-        android:layout_marginBottom="24dp"
-        android:layout_marginEnd="16dp"
+        android:layout_marginStart="@dimen/dialog_side_padding"
+        android:layout_marginEnd="@dimen/dialog_side_padding"
+        android:layout_marginBottom="@dimen/dialog_bottom_padding"
         android:orientation="horizontal">
 
         <Button
             android:id="@+id/stop"
-            style="@style/MediaOutputRoundedOutlinedButton"
+            style="@style/Widget.Dialog.Button.BorderButton"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            android:minWidth="0dp"
+            android:layout_height="wrap_content"
             android:text="@string/keyboard_key_media_stop"
             android:visibility="gone"/>
 
@@ -111,10 +109,9 @@
 
         <Button
             android:id="@+id/done"
-            style="@style/MediaOutputRoundedButton"
+            style="@style/Widget.Dialog.Button"
             android:layout_width="wrap_content"
-            android:layout_height="36dp"
-            android:minWidth="0dp"
+            android:layout_height="wrap_content"
             android:text="@string/inline_done_button"/>
     </LinearLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_output_list_item.xml b/packages/SystemUI/res/layout/media_output_list_item.xml
index 2f5f8d6..8931689 100644
--- a/packages/SystemUI/res/layout/media_output_list_item.xml
+++ b/packages/SystemUI/res/layout/media_output_list_item.xml
@@ -17,7 +17,6 @@
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/device_container"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -65,7 +64,7 @@
             android:layout_marginStart="56dp"
             android:ellipsize="end"
             android:maxLines="1"
-            android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
             android:textSize="16sp"/>
 
         <RelativeLayout
@@ -81,7 +80,8 @@
                 android:layout_height="wrap_content"
                 android:ellipsize="end"
                 android:maxLines="1"
-                android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+                android:textColor="@color/media_dialog_inactive_item_main_content"
                 android:textSize="16sp"/>
             <TextView
                 android:id="@+id/subtitle"
@@ -92,9 +92,9 @@
                 android:layout_alignParentBottom="true"
                 android:ellipsize="end"
                 android:maxLines="1"
-                android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+                android:textColor="@color/media_dialog_inactive_item_main_content"
                 android:textSize="14sp"
-                android:fontFamily="roboto-regular"
+                android:fontFamily="@*android:string/config_bodyFontFamily"
                 android:visibility="gone"/>
             <ImageView
                 android:id="@+id/add_icon"
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index 2ac03c2..f5c6036 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -56,17 +56,4 @@
         layout="@layout/qs_customize_panel"
         android:visibility="gone" />
 
-    <ImageView
-        android:id="@+id/qs_drag_handle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal"
-        android:layout_marginTop="24dp"
-        android:elevation="4dp"
-        android:importantForAccessibility="no"
-        android:scaleType="center"
-        android:src="@drawable/ic_qs_drag_handle"
-        android:tint="@color/qs_detail_button_white"
-        tools:ignore="UseAppTint" />
-
 </com.android.systemui.qs.QSContainerImpl>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index e43a149..f57d65a 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -27,10 +27,10 @@
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:paddingStart="24dp"
-            android:paddingEnd="24dp"
-            android:paddingTop="26dp"
-            android:paddingBottom="30dp"
+            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">
 
             <!-- Header -->
@@ -143,10 +143,7 @@
                     android:layout_weight="0"
                     android:layout_gravity="start"
                     android:text="@string/cancel"
-                    android:textColor="?android:textColorPrimary"
-                    android:background="@drawable/screenrecord_button_background_outline"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textSize="14sp"/>
+                    style="@style/Widget.Dialog.Button.BorderButton" />
                 <Space
                     android:layout_width="0dp"
                     android:layout_height="match_parent"
@@ -158,10 +155,7 @@
                     android:layout_weight="0"
                     android:layout_gravity="end"
                     android:text="@string/screenrecord_start"
-                    android:textColor="@android:color/system_neutral1_900"
-                    android:background="@drawable/screenrecord_button_background_solid"
-                    android:textAppearance="?android:attr/textAppearanceMedium"
-                    android:textSize="14sp"/>
+                    style="@style/Widget.Dialog.Button" />
             </LinearLayout>
         </LinearLayout>
     </ScrollView>
diff --git a/packages/SystemUI/res/layout/status_bar_no_notifications.xml b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
index 332dc6ee..a2abdb2 100644
--- a/packages/SystemUI/res/layout/status_bar_no_notifications.xml
+++ b/packages/SystemUI/res/layout/status_bar_no_notifications.xml
@@ -26,8 +26,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:minHeight="64dp"
-            android:paddingTop="12dp"
             android:textAppearance="?android:attr/textAppearanceButton"
-            android:gravity="top|center_horizontal"
+            android:gravity="center"
             android:text="@string/empty_shade_text"/>
 </com.android.systemui.statusbar.EmptyShadeView>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index f057603..1d6f279 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -22,5 +22,5 @@
     <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">-104dp</dimen>
+    <dimen name="keyguard_large_clock_top_margin">-112dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index e7a158e..3412722 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -65,13 +65,20 @@
     <!-- Media -->
     <color name="media_divider">#85ffffff</color>
 
+    <!-- media output dialog-->
+    <color name="media_dialog_background">@android:color/system_neutral1_900</color>
+    <color name="media_dialog_active_item_main_content">@android:color/system_accent2_800</color>
+    <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_100</color>
+    <color name="media_dialog_item_status">@android:color/system_accent1_100</color>
+    <color name="media_dialog_item_background">@android:color/system_neutral2_800</color>
+
     <!-- Biometric dialog colors -->
     <color name="biometric_dialog_gray">#ffcccccc</color>
     <color name="biometric_dialog_accent">@android:color/system_accent1_300</color>
     <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
 
     <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#ffffff</color> <!-- 100% white -->
+    <color name="udfps_enroll_icon">#7DA7F1</color>
 
     <color name="GM2_green_500">#FF41Af6A</color>
     <color name="GM2_blue_500">#5195EA</color>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 2b16ec2..fc28f09 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,8 +16,7 @@
  * limitations under the License.
  */
 -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
     <drawable name="notification_number_text_color">#ffffffff</drawable>
     <drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
     <color name="system_bar_background_opaque">#ff000000</color>
@@ -135,10 +134,10 @@
     <color name="biometric_dialog_error">#ffd93025</color>                  <!-- red 600 -->
 
     <!-- UDFPS colors -->
-    <color name="udfps_enroll_icon">#000000</color>                         <!-- 100% black -->
-    <color name="udfps_moving_target_fill">#cc4285f4</color>                <!-- 80% blue -->
-    <color name="udfps_enroll_progress">#ff669DF6</color>                   <!-- blue 400 -->
-    <color name="udfps_enroll_progress_help">#ffEE675C</color>              <!-- red 400 -->
+    <color name="udfps_enroll_icon">#7DA7F1</color>
+    <color name="udfps_moving_target_fill">#475670</color>
+    <color name="udfps_enroll_progress">#7DA7F1</color>
+    <color name="udfps_enroll_progress_help">#ffEE675C</color>
 
     <!-- Global screenshot actions -->
     <color name="screenshot_button_ripple">#1f000000</color>
@@ -173,6 +172,13 @@
     <!-- media -->
     <color name="media_seamless_border">?android:attr/colorAccent</color>
 
+    <!-- media output dialog-->
+    <color name="media_dialog_background" android:lstar="98">@android:color/system_neutral1_100</color>
+    <color name="media_dialog_active_item_main_content">@android:color/system_accent1_900</color>
+    <color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_600</color>
+    <color name="media_dialog_item_status">@android:color/system_accent1_900</color>
+    <color name="media_dialog_item_background">@android:color/system_accent2_50</color>
+
     <!-- controls -->
     <color name="control_primary_text">#E6FFFFFF</color>
     <color name="control_secondary_text">#99FFFFFF</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ff748a9..3695286 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -598,17 +598,6 @@
         280
     </integer>
 
-    <!-- Haptic feedback intensity for ticks used for the udfps dwell time -->
-    <item name="config_udfpsTickIntensity" translatable="false" format="float"
-          type="dimen">.5</item>
-
-    <!-- Haptic feedback delay between ticks used for udfps dwell time -->
-    <integer name="config_udfpsTickDelay" translatable="false">25</integer>
-
-    <!-- Haptic feedback tick type - if true, uses VibrationEffect.Composition.PRIMITIVE_LOW_TICK
-         else uses VibrationEffect.Composition.PRIMITIVE_TICK -->
-    <bool name="config_udfpsUseLowTick">true</bool>
-
     <!-- package name of a built-in camera app to use to restrict implicit intent resolution
          when the double-press power gesture is used. Ignored if empty. -->
     <string translatable="false" name="config_cameraGesturePackage"></string>
@@ -742,4 +731,8 @@
 
     <!-- Flag to enable dream overlay service and its registration -->
     <bool name="config_dreamOverlayServiceEnabled">false</bool>
+
+    <!-- Class for the communal source connector to be used -->
+    <string name="config_communalSourceConnector" translatable="false"></string>
+
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0b56264..843c69f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -287,6 +287,7 @@
     <dimen name="screenshot_action_chip_text_size">14sp</dimen>
     <dimen name="screenshot_dismissal_height_delta">80dp</dimen>
     <dimen name="screenshot_crop_handle_thickness">3dp</dimen>
+    <dimen name="long_screenshot_action_bar_top_margin">8dp</dimen>
 
 
     <!-- The width of the view containing navigation buttons -->
@@ -601,7 +602,7 @@
     <!-- When large clock is showing, offset the smartspace by this amount -->
     <dimen name="keyguard_smartspace_top_offset">12dp</dimen>
     <!-- With the large clock, move up slightly from the center -->
-    <dimen name="keyguard_large_clock_top_margin">-52dp</dimen>
+    <dimen name="keyguard_large_clock_top_margin">-60dp</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>
@@ -1097,8 +1098,6 @@
     <dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
     <dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
     <dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
-    <dimen name="media_output_dialog_button_padding_horizontal">16dp</dimen>
-    <dimen name="media_output_dialog_button_padding_vertical">8dp</dimen>
 
     <!-- Distance that the full shade transition takes in order for qs to fully transition to the
          shade -->
@@ -1304,4 +1303,9 @@
     <dimen name="keyguard_unfold_translation_x">16dp</dimen>
 
     <dimen name="fgs_manager_min_width_minor">100%</dimen>
+
+    <!-- Dream overlay related dimensions -->
+    <dimen name="dream_overlay_status_bar_height">80dp</dimen>
+    <dimen name="dream_overlay_status_bar_margin">40dp</dimen>
+    <dimen name="dream_overlay_status_icon_margin">8dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4790412..99508a5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -588,9 +588,11 @@
     <string name="quick_settings_camera_label">Camera access</string>
     <!-- QuickSettings: Microphone [CHAR LIMIT=NONE] -->
     <string name="quick_settings_mic_label">Mic access</string>
-    <!-- QuickSettings: Camera or microphone access is allowed [CHAR LIMIT=NONE] -->
+    <!-- QuickSettings: Camera or microphone access is allowed. If you update this string, please
+    update the string "available" in packages/modules/Permission/PermissionController[CHAR LIMIT=NONE] -->
     <string name="quick_settings_camera_mic_available">Available</string>
-    <!-- QuickSettings: Camera or microphone access is blocked [CHAR LIMIT=NONE] -->
+    <!-- QuickSettings: Camera or microphone access is blocked. If you update this string, please
+    update the string "blocked" in packages/modules/Permission/PermissionController [CHAR LIMIT=NONE] -->
     <string name="quick_settings_camera_mic_blocked">Blocked</string>
     <!-- QuickSettings: Media device [CHAR LIMIT=NONE] -->
     <string name="quick_settings_media_device_label">Media device</string>
@@ -632,7 +634,7 @@
     <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
     <string name="quick_settings_brightness_dialog_title">Brightness</string>
     <!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_inversion_label">Invert colors</string>
+    <string name="quick_settings_inversion_label">Color inversion</string>
     <!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_more_settings">More settings</string>
@@ -2048,8 +2050,8 @@
     <string name="magnification_mode_switch_click_label">Switch</string>
 
     <!-- Accessibility floating menu strings -->
-    <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] -->
-    <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string>
+    <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
+    <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
     <!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
     <string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
     <!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
@@ -2164,6 +2166,15 @@
     <!-- Content description for media cotnrols progress bar [CHAR_LIMIT=NONE] -->
     <string name="controls_media_seekbar_description"><xliff:g id="elapsed_time" example="1:30">%1$s</xliff:g> of <xliff:g id="total_time" example="3:00">%2$s</xliff:g></string>
 
+    <!-- Description for button in media controls. Pressing button starts playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_play">Play</string>
+    <!-- Description for button in media controls. Pressing button pauses playback [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_pause">Pause</string>
+    <!-- Description for button in media controls. Pressing button goes to previous track [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_prev">Previous track</string>
+    <!-- Description for button in media controls. Pressing button goes to next track [CHAR_LIMIT=NONE] -->
+    <string name="controls_media_button_next">Next track</string>
+
     <!-- Title for Smartspace recommendation card within media controls. The "Play" means the action to play a media [CHAR_LIMIT=10] -->
     <string name="controls_media_smartspace_rec_title">Play</string>
     <!-- Description for Smartspace recommendation card within media controls [CHAR_LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2c79919..9358349 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -450,14 +450,13 @@
         <item name="android:background">@drawable/btn_borderless_rect</item>
     </style>
 
-    <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
-        <item name="android:background">@drawable/media_output_dialog_button_background</item>
+    <style name="Theme.SystemUI.Dialog.Media" parent="Theme.SystemUI.Dialog">
+        <item name="android:colorBackground">@color/media_dialog_background</item>
     </style>
 
-    <style name="MediaOutputRoundedButton" parent="@android:style/Widget.Material.Button">
-        <item name="android:background">@drawable/media_output_dialog_solid_button_background</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
-        <item name="android:textSize">14sp</item>
+    <style name="MediaOutputItemInactiveTitle">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textColor">@color/media_dialog_inactive_item_main_content</item>
     </style>
 
     <style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings">
@@ -887,13 +886,18 @@
         <item name="android:textAlignment">center</item>
     </style>
 
-    <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog">
+
+    <style name="Widget" />
+    <style name="Widget.Dialog" />
+    <style name="Widget.Dialog.Button">
+        <item name="android:buttonCornerRadius">28dp</item>
         <item name="android:background">@drawable/qs_dialog_btn_filled</item>
         <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
         <item name="android:textSize">14sp</item>
         <item name="android:lineHeight">20sp</item>
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:stateListAnimator">@null</item>
+        <item name="android:minWidth">0dp</item>
     </style>
 
     <style name="Widget.Dialog.Button.BorderButton">
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index d61e4a9..91607d2 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -23,11 +23,22 @@
         app:constraintSetEnd="@id/qs_header_constraint"
         app:constraintSetStart="@id/qqs_header_constraint">
         <KeyFrameSet>
+            <!-- These positions are to prevent visual movement of @id/date -->
             <KeyPosition
                 app:keyPositionType="pathRelative"
                 app:percentX="0"
-                app:framePosition="50"
+                app:framePosition="49"
                 app:motionTarget="@id/date" />
+            <KeyPosition
+                app:keyPositionType="pathRelative"
+                app:percentX="1"
+                app:framePosition="51"
+                app:motionTarget="@id/date" />
+            <KeyAttribute
+                app:motionTarget="@id/date"
+                app:framePosition="50"
+                android:alpha="0"
+                />
         </KeyFrameSet>
     </Transition>
 
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 3d7b549..c5b4c5d 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -49,6 +49,14 @@
     </Constraint>
 
     <Constraint
+        android:id="@+id/statusIcons">
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/batteryRemainingIcon" >
+    </Constraint>
+
+    <Constraint
         android:id="@+id/carrier_group">
         <CustomAttribute
             app:attributeName="alpha"
@@ -56,6 +64,15 @@
         />
     </Constraint>
 
-
+    <Constraint
+        android:id="@+id/privacy_container">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="@id/date"
+        />
+    </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 6a0ab86..8248fcd 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -58,5 +58,14 @@
         />
     </Constraint>
 
-
+    <Constraint
+        android:id="@+id/privacy_container">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="48dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="@id/date"
+        />
+    </Constraint>
 </ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/split_header.xml b/packages/SystemUI/res/xml/split_header.xml
index 44d42a0..03401b3 100644
--- a/packages/SystemUI/res/xml/split_header.xml
+++ b/packages/SystemUI/res/xml/split_header.xml
@@ -53,5 +53,29 @@
         />
     </Constraint>
 
+    <Constraint
+        android:id="@+id/batteryRemainingIcon">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            app:layout_constraintHeight_min="@dimen/split_shade_header_min_height"
+            app:layout_constraintStart_toEndOf="@id/statusIcons"
+            app:layout_constraintEnd_toStartOf="@id/privacy_container"
+            app:layout_constraintTop_toTopOf="@id/clock"
+            app:layout_constraintBottom_toBottomOf="parent"
+        />
+    </Constraint>
+
+    <Constraint
+        android:id="@+id/privacy_container">
+        <Layout
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@id/date"
+            app:layout_constraintBottom_toBottomOf="@id/date"
+            app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
+        />
+    </Constraint>
 
 </ConstraintSet>
\ 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 9574101..b611c96 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -16,28 +16,31 @@
 
 package com.android.systemui.flags
 
+import android.annotation.BoolRes
+import android.annotation.IntegerRes
+import android.annotation.StringRes
 import android.os.Parcel
 import android.os.Parcelable
 
-interface Flag<T> : Parcelable {
+interface Flag<T> {
     val id: Int
+}
+
+interface ParcelableFlag<T> : Flag<T>, Parcelable {
     val default: T
-    val resourceOverride: Int
-
     override fun describeContents() = 0
+}
 
-    fun hasResourceOverride(): Boolean {
-        return resourceOverride != -1
-    }
+interface ResourceFlag<T> : Flag<T> {
+    val resourceId: Int
 }
 
 // Consider using the "parcelize" kotlin library.
 
 data class BooleanFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Boolean = false,
-    override val resourceOverride: Int = -1
-) : Flag<Boolean> {
+    override val default: Boolean = false
+) : ParcelableFlag<Boolean> {
 
     companion object {
         @JvmField
@@ -58,11 +61,15 @@
     }
 }
 
+data class ResourceBooleanFlag constructor(
+    override val id: Int,
+    @BoolRes override val resourceId: Int
+) : ResourceFlag<Boolean>
+
 data class StringFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: String = "",
-    override val resourceOverride: Int = -1
-) : Flag<String> {
+    override val default: String = ""
+) : ParcelableFlag<String> {
     companion object {
         @JvmField
         val CREATOR = object : Parcelable.Creator<StringFlag> {
@@ -82,11 +89,15 @@
     }
 }
 
+data class ResourceStringFlag constructor(
+    override val id: Int,
+    @StringRes override val resourceId: Int
+) : ResourceFlag<String>
+
 data class IntFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Int = 0,
-    override val resourceOverride: Int = -1
-) : Flag<Int> {
+    override val default: Int = 0
+) : ParcelableFlag<Int> {
 
     companion object {
         @JvmField
@@ -107,11 +118,15 @@
     }
 }
 
+data class ResourceIntFlag constructor(
+    override val id: Int,
+    @IntegerRes override val resourceId: Int
+) : ResourceFlag<Int>
+
 data class LongFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Long = 0,
-    override val resourceOverride: Int = -1
-) : Flag<Long> {
+    override val default: Long = 0
+) : ParcelableFlag<Long> {
 
     companion object {
         @JvmField
@@ -134,9 +149,8 @@
 
 data class FloatFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Float = 0f,
-    override val resourceOverride: Int = -1
-) : Flag<Float> {
+    override val default: Float = 0f
+) : ParcelableFlag<Float> {
 
     companion object {
         @JvmField
@@ -157,11 +171,15 @@
     }
 }
 
+data class ResourceFloatFlag constructor(
+    override val id: Int,
+    override val resourceId: Int
+) : ResourceFlag<Int>
+
 data class DoubleFlag @JvmOverloads constructor(
     override val id: Int,
-    override val default: Double = 0.0,
-    override val resourceOverride: Int = -1
-) : Flag<Double> {
+    override val default: Double = 0.0
+) : ParcelableFlag<Double> {
 
     companion object {
         @JvmField
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
similarity index 61%
rename from packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
rename to packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
index 91a3912..195ba465 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagReader.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt
@@ -18,26 +18,24 @@
 /**
  * Plugin for loading flag values
  */
-interface FlagReader {
-    /** Returns a boolean value for the given flag.  */
-    fun isEnabled(flag: BooleanFlag): Boolean {
-        return flag.default
-    }
-
-    /** Returns a boolean value for the given flag.  */
-    fun isEnabled(id: Int, def: Boolean): Boolean {
-        return def
-    }
-
-    /** Add a listener to be alerted when any flag changes.  */
-    fun addListener(listener: Listener) {}
+interface FlagListenable {
+    /** Add a listener to be alerted when the given flag changes.  */
+    fun addListener(flag: Flag<*>, listener: Listener)
 
     /** Remove a listener to be alerted when any flag changes.  */
-    fun removeListener(listener: Listener) {}
+    fun removeListener(listener: Listener)
 
     /** A simple listener to be alerted when a flag changes.  */
     fun interface Listener {
-        /**  */
-        fun onFlagChanged(id: Int)
+        /** Called when the flag changes */
+        fun onFlagChanged(event: FlagEvent)
+    }
+
+    /** An event representing the change */
+    interface FlagEvent {
+        /** the id of the flag which changed */
+        val flagId: Int
+        /** if all listeners alerted invoke this method, the restart will be skipped */
+        fun requestNoRestart()
     }
 }
\ No newline at end of file
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 e61cb5c..ec619dd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -24,30 +24,39 @@
 import android.net.Uri
 import android.os.Bundle
 import android.os.Handler
-import android.provider.Settings
 import androidx.concurrent.futures.CallbackToFutureAdapter
 import com.google.common.util.concurrent.ListenableFuture
-import org.json.JSONException
-import org.json.JSONObject
+import java.util.function.Consumer
 
 class FlagManager constructor(
     private val context: Context,
+    private val settings: FlagSettingsHelper,
     private val handler: Handler
-) : FlagReader {
+) : FlagListenable {
     companion object {
         const val RECEIVING_PACKAGE = "com.android.systemui"
         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 FIELD_ID = "id"
-        const val FIELD_VALUE = "value"
-        const val FIELD_TYPE = "type"
-        const val FIELD_FLAGS = "flags"
-        const val TYPE_BOOLEAN = "boolean"
+        const val EXTRA_ID = "id"
+        const val EXTRA_VALUE = "value"
+        const val EXTRA_FLAGS = "flags"
         private const val SETTINGS_PREFIX = "systemui/flags"
     }
 
-    private val listeners: MutableSet<FlagReader.Listener> = mutableSetOf()
+    constructor(context: Context, handler: Handler) : this(
+        context,
+        FlagSettingsHelper(context.contentResolver),
+        handler
+    )
+
+    /**
+     * An action called on restart which takes as an argument whether the listeners requested
+     * that the restart be suppressed
+     */
+    var restartAction: Consumer<Boolean>? = null
+    var clearCacheAction: Consumer<Int>? = null
+    private val listeners: MutableSet<PerFlagListener> = mutableSetOf()
     private val settingsObserver: ContentObserver = SettingsObserver()
 
     fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
@@ -60,8 +69,8 @@
                     object : BroadcastReceiver() {
                         override fun onReceive(context: Context, intent: Intent) {
                             val extras: Bundle? = getResultExtras(false)
-                            val listOfFlags: java.util.ArrayList<Flag<*>>? =
-                                extras?.getParcelableArrayList(FIELD_FLAGS)
+                            val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
+                                extras?.getParcelableArrayList(EXTRA_FLAGS)
                             if (listOfFlags != null) {
                                 completer.set(listOfFlags)
                             } else {
@@ -73,9 +82,19 @@
         } as ListenableFuture<Collection<Flag<*>>>
     }
 
+    /**
+     * Returns the stored value or null if not set.
+     * This API is used by TheFlippinApp.
+     */
+    fun isEnabled(id: Int): Boolean? = readFlagValue(id, BooleanFlagSerializer)
+
+    /**
+     * Sets the value of a boolean flag.
+     * This API is used by TheFlippinApp.
+     */
     fun setFlagValue(id: Int, enabled: Boolean) {
         val intent = createIntent(id)
-        intent.putExtra(FIELD_VALUE, enabled)
+        intent.putExtra(EXTRA_VALUE, enabled)
 
         context.sendBroadcast(intent)
     }
@@ -86,45 +105,30 @@
         context.sendBroadcast(intent)
     }
 
-    override fun isEnabled(id: Int, def: Boolean): Boolean {
-        return isEnabled(id) ?: def
-    }
-
     /** Returns the stored value or null if not set.  */
-    fun isEnabled(id: Int): Boolean? {
-        val data: String? = Settings.Secure.getString(
-            context.contentResolver, keyToSettingsPrefix(id))
-        if (data == null || data?.isEmpty()) {
-            return null
-        }
-        val json: JSONObject
-        try {
-            json = JSONObject(data)
-            return if (!assertType(json, TYPE_BOOLEAN)) {
-                null
-            } else json.getBoolean(FIELD_VALUE)
-        } catch (e: JSONException) {
-            throw InvalidFlagStorageException()
-        }
+    fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
+        val data = settings.getString(idToSettingsKey(id))
+        return serializer.fromSettingsData(data)
     }
 
-    override fun addListener(listener: FlagReader.Listener) {
+    override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
         synchronized(listeners) {
             val registerNeeded = listeners.isEmpty()
-            listeners.add(listener)
+            listeners.add(PerFlagListener(flag.id, listener))
             if (registerNeeded) {
-                context.contentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(SETTINGS_PREFIX), true, settingsObserver)
+                settings.registerContentObserver(SETTINGS_PREFIX, true, settingsObserver)
             }
         }
     }
 
-    override fun removeListener(listener: FlagReader.Listener) {
+    override fun removeListener(listener: FlagListenable.Listener) {
         synchronized(listeners) {
-            val isRegistered = !listeners.isEmpty()
-            listeners.remove(listener)
-            if (isRegistered && listeners.isEmpty()) {
-                context.contentResolver.unregisterContentObserver(settingsObserver)
+            if (listeners.isEmpty()) {
+                return
+            }
+            listeners.removeIf { it.listener == listener }
+            if (listeners.isEmpty()) {
+                settings.unregisterContentObserver(settingsObserver)
             }
         }
     }
@@ -132,21 +136,13 @@
     private fun createIntent(id: Int): Intent {
         val intent = Intent(ACTION_SET_FLAG)
         intent.setPackage(RECEIVING_PACKAGE)
-        intent.putExtra(FIELD_ID, id)
+        intent.putExtra(EXTRA_ID, id)
 
         return intent
     }
 
-    fun keyToSettingsPrefix(key: Int): String {
-        return SETTINGS_PREFIX + "/" + key
-    }
-
-    private fun assertType(json: JSONObject, type: String): Boolean {
-        return try {
-            json.getString(FIELD_TYPE) == TYPE_BOOLEAN
-        } catch (e: JSONException) {
-            false
-        }
+    fun idToSettingsKey(id: Int): String {
+        return "$SETTINGS_PREFIX/$id"
     }
 
     inner class SettingsObserver : ContentObserver(handler) {
@@ -156,17 +152,40 @@
             }
             val parts = uri.pathSegments
             val idStr = parts[parts.size - 1]
-            try {
-                val id = idStr.toInt()
-                listeners.forEach { l -> l.onFlagChanged(id) }
-            } catch (e: NumberFormatException) {
-                // no-op
-            }
+            val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
+            clearCacheAction?.accept(id)
+            dispatchListenersAndMaybeRestart(id)
         }
     }
-}
 
-class InvalidFlagStorageException : Exception("Data found but is invalid")
+    fun dispatchListenersAndMaybeRestart(id: Int) {
+        val filteredListeners: List<FlagListenable.Listener> = synchronized(listeners) {
+            listeners.mapNotNull { if (it.id == id) it.listener else null }
+        }
+        // If there are no listeners, there's nothing to dispatch to, and nothing to suppress it.
+        if (filteredListeners.isEmpty()) {
+            restartAction?.accept(false)
+            return
+        }
+        // Dispatch to every listener and save whether each one called requestNoRestart.
+        val suppressRestartList: List<Boolean> = filteredListeners.map { listener ->
+            var didRequestNoRestart = false
+            val event = object : FlagListenable.FlagEvent {
+                override val flagId = id
+                override fun requestNoRestart() {
+                    didRequestNoRestart = true
+                }
+            }
+            listener.onFlagChanged(event)
+            didRequestNoRestart
+        }
+        // Suppress restart only if ALL listeners request it.
+        val suppressRestart = suppressRestartList.all { it }
+        restartAction?.accept(suppressRestart)
+    }
+
+    private data class PerFlagListener(val id: Int, val listener: FlagListenable.Listener)
+}
 
 class NoFlagResultsException : Exception(
     "SystemUI failed to communicate its flags back successfully")
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
new file mode 100644
index 0000000..e9ea19d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.util.Log
+import org.json.JSONException
+import org.json.JSONObject
+
+private const val FIELD_VALUE = "value"
+private const val FIELD_TYPE = "type"
+private const val TYPE_BOOLEAN = "boolean"
+private const val TYPE_STRING = "string"
+
+private const val TAG = "FlagSerializer"
+
+abstract class FlagSerializer<T>(
+    private val type: String,
+    private val setter: (JSONObject, String, T) -> Unit,
+    private val getter: (JSONObject, String) -> T
+) {
+    fun toSettingsData(value: T): String? {
+        return try {
+            JSONObject()
+                .put(FIELD_TYPE, type)
+                .also { setter(it, FIELD_VALUE, value) }
+                .toString()
+        } catch (e: JSONException) {
+            Log.w(TAG, "write error", e)
+            null
+        }
+    }
+
+    /**
+     * @throws InvalidFlagStorageException
+     */
+    fun fromSettingsData(data: String?): T? {
+        if (data == null || data.isEmpty()) {
+            return null
+        }
+        try {
+            val json = JSONObject(data)
+            return if (json.getString(FIELD_TYPE) == type) {
+                getter(json, FIELD_VALUE)
+            } else {
+                null
+            }
+        } catch (e: JSONException) {
+            Log.w(TAG, "read error", e)
+            throw InvalidFlagStorageException()
+        }
+    }
+}
+
+object BooleanFlagSerializer : FlagSerializer<Boolean>(
+    TYPE_BOOLEAN,
+    JSONObject::put,
+    JSONObject::getBoolean
+)
+
+object StringFlagSerializer : FlagSerializer<String>(
+    TYPE_STRING,
+    JSONObject::put,
+    JSONObject::getString
+)
+
+class InvalidFlagStorageException : Exception("Data found but is invalid")
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
new file mode 100644
index 0000000..742bb0b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+
+class FlagSettingsHelper(private val contentResolver: ContentResolver) {
+
+    fun getString(key: String): String? = Settings.Secure.getString(contentResolver, key)
+
+    fun registerContentObserver(
+        name: String,
+        notifyForDescendants: Boolean,
+        observer: ContentObserver
+    ) {
+        contentResolver.registerContentObserver(
+            Settings.Secure.getUriFor(name),
+            notifyForDescendants,
+            observer
+        )
+    }
+
+    fun unregisterContentObserver(observer: ContentObserver) {
+        contentResolver.unregisterContentObserver(observer)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7d0fb5d..567e7aa 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -53,8 +53,8 @@
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
                 .setPosition(leash, positionX, positionY)
                 .setCornerRadius(leash, cornerRadius);
-        return new PictureInPictureSurfaceTransaction(
-                positionX, positionY, mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
+        return newPipSurfaceTransaction(positionX, positionY,
+                mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds);
     }
 
     public PictureInPictureSurfaceTransaction scale(
@@ -70,8 +70,8 @@
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
                 .setPosition(leash, positionX, positionY)
                 .setCornerRadius(leash, cornerRadius);
-        return new PictureInPictureSurfaceTransaction(
-                positionX, positionY, mTmpFloat9, degree, cornerRadius, sourceBounds);
+        return newPipSurfaceTransaction(positionX, positionY,
+                mTmpFloat9, degree, cornerRadius, sourceBounds);
     }
 
     public PictureInPictureSurfaceTransaction scaleAndCrop(
@@ -93,8 +93,8 @@
                 .setWindowCrop(leash, mTmpDestinationRect)
                 .setPosition(leash, left, top)
                 .setCornerRadius(leash, cornerRadius);
-        return new PictureInPictureSurfaceTransaction(
-                left, top, mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
+        return newPipSurfaceTransaction(left, top,
+                mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect);
     }
 
     public PictureInPictureSurfaceTransaction scaleAndRotate(
@@ -125,8 +125,7 @@
                 .setWindowCrop(leash, mTmpDestinationRect)
                 .setPosition(leash, adjustedPositionX, adjustedPositionY)
                 .setCornerRadius(leash, cornerRadius);
-        return new PictureInPictureSurfaceTransaction(
-                adjustedPositionX, adjustedPositionY,
+        return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY,
                 mTmpFloat9, degree, cornerRadius, mTmpDestinationRect);
     }
 
@@ -137,6 +136,17 @@
         return mCornerRadius * scale;
     }
 
+    private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction(
+            float posX, float posY, float[] float9, float rotation, float cornerRadius,
+            Rect windowCrop) {
+        return new PictureInPictureSurfaceTransaction.Builder()
+                .setPosition(posX, posY)
+                .setTransform(float9, rotation)
+                .setCornerRadius(cornerRadius)
+                .setWindowCrop(windowCrop)
+                .build();
+    }
+
     /** @return {@link SurfaceControl.Transaction} instance with vsync-id */
     public static SurfaceControl.Transaction newSurfaceControlTransaction() {
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
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 be15c70..b3983d2 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
@@ -155,5 +155,10 @@
      */
     void onImeSwitcherPressed() = 49;
 
-    // Next id = 50
+    /**
+     * Notifies to toggle notification panel.
+     */
+    void toggleNotificationPanel() = 50;
+
+    // Next id = 51
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 78867f7..605d376 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
 import android.view.IRotationWatcher;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -453,6 +454,7 @@
         mUiEventLogger.log(RotationButtonEvent.ROTATION_SUGGESTION_ACCEPTED);
         incrementNumAcceptedRotationSuggestionsIfNeeded();
         setRotationLockedAtAngle(mLastRotationSuggestion);
+        v.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
     }
 
     private boolean onRotateSuggestionHover(View v, MotionEvent event) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 38eded8..48fcbbd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -61,7 +61,7 @@
 public class ActivityManagerWrapper {
 
     private static final String TAG = "ActivityManagerWrapper";
-
+    private static final int NUM_RECENT_ACTIVITIES_REQUEST = 3;
     private static final ActivityManagerWrapper sInstance = new ActivityManagerWrapper();
 
     // Should match the values in PhoneWindowManager
@@ -113,6 +113,22 @@
     }
 
     /**
+     * We ask for {@link #NUM_RECENT_ACTIVITIES_REQUEST} activities because when in split screen,
+     * we'll get back 2 activities for each split app and one for launcher. Launcher might be more
+     * "recently" used than one of the split apps so if we only request 2 tasks, then we might miss
+     * out on one of the split apps
+     *
+     * @return an array of up to {@link #NUM_RECENT_ACTIVITIES_REQUEST} running tasks
+     *         filtering only for tasks that can be visible in the recent tasks list.
+     */
+    public ActivityManager.RunningTaskInfo[] getRunningTasks(boolean filterOnlyVisibleRecents) {
+        // Note: The set of running tasks from the system is ordered by recency
+        List<ActivityManager.RunningTaskInfo> tasks =
+                mAtm.getTasks(NUM_RECENT_ACTIVITIES_REQUEST, filterOnlyVisibleRecents);
+        return tasks.toArray(new RunningTaskInfo[tasks.size()]);
+    }
+
+    /**
      * @return a list of the recents tasks.
      */
     public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java
index 443c1e1..5c37ecce 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/PackageManagerWrapper.java
@@ -22,7 +22,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.PackageManager.ResolveInfoFlagsBits;
 import android.content.pm.ResolveInfo;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -73,7 +73,7 @@
     /**
      * Determine the best Activity to perform for a given Intent.
      */
-    public ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlags int flags) {
+    public ResolveInfo resolveActivity(Intent intent, @ResolveInfoFlagsBits int flags) {
         final String resolvedType =
                 intent.resolveTypeIfNeeded(AppGlobals.getInitialApplication().getContentResolver());
         try {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 30db136..c1d9d0d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -65,6 +65,7 @@
     public final Rect localBounds;
     public final Rect sourceContainerBounds;
     public final Rect screenSpaceBounds;
+    public final Rect startScreenSpaceBounds;
     public final boolean isNotInRecents;
     public final Rect contentInsets;
     public final ActivityManager.RunningTaskInfo taskInfo;
@@ -88,6 +89,7 @@
         localBounds = app.localBounds;
         sourceContainerBounds = app.sourceContainerBounds;
         screenSpaceBounds = app.screenSpaceBounds;
+        startScreenSpaceBounds = screenSpaceBounds;
         prefixOrderIndex = app.prefixOrderIndex;
         isNotInRecents = app.isNotInRecents;
         contentInsets = app.contentInsets;
@@ -219,6 +221,8 @@
         localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
         sourceContainerBounds = null;
         screenSpaceBounds = new Rect(change.getEndAbsBounds());
+        startScreenSpaceBounds = new Rect(change.getStartAbsBounds());
+
         prefixOrderIndex = order;
         // TODO(shell-transitions): I guess we need to send content insets? evaluate how its used.
         contentInsets = new Rect(0, 0, 0, 0);
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 a319b40..3cd090e 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
@@ -270,7 +270,7 @@
                 mOpeningLeashes.add(openingTasks.get(i).getLeash());
                 // We are receiving new opening tasks, so convert to onTasksAppeared.
                 final RemoteAnimationTargetCompat target = new RemoteAnimationTargetCompat(
-                        openingTasks.get(i), layer, mInfo, t);
+                        openingTasks.get(i), layer, info, t);
                 mLeashMap.put(mOpeningLeashes.get(i), target.leash.mSurfaceControl);
                 t.reparent(target.leash.mSurfaceControl, mInfo.getRootLeash());
                 t.setLayer(target.leash.mSurfaceControl, layer);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index b6be6ed..ac946ca 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -26,12 +26,19 @@
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
 import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
+import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
 import java.lang.IllegalStateException
 import java.util.concurrent.Executor
 
+/**
+ * Factory for [UnfoldTransitionProgressProvider].
+ *
+ * This is needed as Launcher has to create the object manually.
+ * Sysui create it using dagger (see [UnfoldTransitionModule]).
+ */
 fun createUnfoldTransitionProgressProvider(
     context: Context,
     config: UnfoldTransitionConfig,
@@ -59,17 +66,19 @@
         hingeAngleProvider,
         screenStatusProvider,
         deviceStateManager,
-        mainExecutor
+        mainExecutor,
+        mainHandler
     )
 
-    return if (config.isHingeAngleEnabled) {
-        PhysicsBasedUnfoldTransitionProgressProvider(
-            mainHandler,
-            foldStateProvider
-        )
+    val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
+        PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
     } else {
         FixedTimingTransitionProgressProvider(foldStateProvider)
     }
+    return ScaleAwareTransitionProgressProvider(
+            unfoldTransitionProgressProvider,
+            context.contentResolver
+    )
 }
 
 fun createConfig(context: Context): UnfoldTransitionConfig =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 90f5998..51eae57 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.unfold.progress
 
-import android.os.Handler
 import android.util.Log
 import android.util.MathUtils.saturate
 import androidx.dynamicanimation.animation.DynamicAnimation
@@ -24,9 +23,10 @@
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED
 import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
 import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -39,7 +39,6 @@
  *  - doesn't handle postures
  */
 internal class PhysicsBasedUnfoldTransitionProgressProvider(
-    private val handler: Handler,
     private val foldStateProvider: FoldStateProvider
 ) :
     UnfoldTransitionProgressProvider,
@@ -51,8 +50,6 @@
             addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
         }
 
-    private val timeoutRunnable = TimeoutRunnable()
-
     private var isTransitionRunning = false
     private var isAnimatedCancelRunning = false
 
@@ -92,7 +89,7 @@
                     cancelTransition(endValue = 1f, animate = true)
                 }
             }
-            FOLD_UPDATE_FINISH_FULL_OPEN -> {
+            FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> {
                 // Do not cancel if we haven't started the transition yet.
                 // This could happen when we fully unfolded the device before the screen
                 // became available. In this case we start and immediately cancel the animation
@@ -106,7 +103,11 @@
                 cancelTransition(endValue = 0f, animate = false)
             }
             FOLD_UPDATE_START_CLOSING -> {
-                startTransition(startValue = 1f)
+                // The transition might be already running as the device might start closing several
+                // times before reaching an end state.
+                if (!isTransitionRunning) {
+                    startTransition(startValue = 1f)
+                }
             }
         }
 
@@ -116,8 +117,6 @@
     }
 
     private fun cancelTransition(endValue: Float, animate: Boolean) {
-        handler.removeCallbacks(timeoutRunnable)
-
         if (isTransitionRunning && animate) {
             isAnimatedCancelRunning = true
             springAnimation.animateToFinalPosition(endValue)
@@ -175,8 +174,6 @@
         }
 
         springAnimation.start()
-
-        handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
     }
 
     override fun addCallback(listener: TransitionProgressListener) {
@@ -187,13 +184,6 @@
         listeners.remove(listener)
     }
 
-    private inner class TimeoutRunnable : Runnable {
-
-        override fun run() {
-            cancelTransition(endValue = 1f, animate = true)
-        }
-    }
-
     private object AnimationProgressProperty :
         FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
 
@@ -212,7 +202,6 @@
 private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
 private const val DEBUG = true
 
-private const val TRANSITION_TIMEOUT_MILLIS = 2000L
 private const val SPRING_STIFFNESS = 200.0f
 private const val MINIMAL_VISIBLE_CHANGE = 0.001f
 private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 35e2b30..6d9631c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,14 +15,19 @@
  */
 package com.android.systemui.unfold.updates
 
+import android.annotation.FloatRange
 import android.content.Context
 import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import android.util.Log
+import androidx.annotation.VisibleForTesting
 import androidx.core.util.Consumer
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
 import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import java.util.concurrent.Executor
 
 class DeviceFoldStateProvider(
@@ -30,7 +35,8 @@
     private val hingeAngleProvider: HingeAngleProvider,
     private val screenStatusProvider: ScreenStatusProvider,
     private val deviceStateManager: DeviceStateManager,
-    private val mainExecutor: Executor
+    private val mainExecutor: Executor,
+    private val handler: Handler
 ) : FoldStateProvider {
 
     private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
@@ -38,9 +44,13 @@
     @FoldUpdate
     private var lastFoldUpdate: Int? = null
 
+    @FloatRange(from = 0.0, to = 180.0)
+    private var lastHingeAngle: Float = 0f
+
     private val hingeAngleListener = HingeAngleListener()
     private val screenListener = ScreenStatusListener()
     private val foldStateListener = FoldStateListener(context)
+    private val timeoutRunnable = TimeoutRunnable()
 
     private var isFolded = false
     private var isUnfoldHandled = true
@@ -72,47 +82,69 @@
     override val isFullyOpened: Boolean
         get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
 
+    private val isTransitionInProgess: Boolean
+        get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
+                lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
     private fun onHingeAngle(angle: Float) {
-        when (lastFoldUpdate) {
-            FOLD_UPDATE_FINISH_FULL_OPEN -> {
-                if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
-                    lastFoldUpdate = FOLD_UPDATE_START_CLOSING
-                    outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
-                }
-            }
-            FOLD_UPDATE_START_OPENING -> {
-                if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
-                    lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
-                    outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
-                }
-            }
-            FOLD_UPDATE_START_CLOSING -> {
-                if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
-                    lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
-                    outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
-                }
+        if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
+
+        val isClosing = angle < lastHingeAngle
+        val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
+        val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
+        if (isClosing && !closingEventDispatched && !isFullyOpened) {
+            notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
+        }
+
+        if (isTransitionInProgess) {
+            if (isFullyOpened) {
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+                cancelTimeout()
+            } else {
+                // The timeout will trigger some constant time after the last angle update.
+                rescheduleAbortAnimationTimeout()
             }
         }
 
+        lastHingeAngle = angle
         outputListeners.forEach { it.onHingeAngleUpdate(angle) }
     }
 
     private inner class FoldStateListener(context: Context) :
         DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
             isFolded = folded
+            lastHingeAngle = FULLY_CLOSED_DEGREES
 
             if (folded) {
-                lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
-                outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
                 hingeAngleProvider.stop()
+                notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+                cancelTimeout()
                 isUnfoldHandled = false
             } else {
-                lastFoldUpdate = FOLD_UPDATE_START_OPENING
-                outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
+                notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+                rescheduleAbortAnimationTimeout()
                 hingeAngleProvider.start()
             }
         })
 
+    private fun notifyFoldUpdate(@FoldUpdate update: Int) {
+        if (DEBUG) { Log.d(TAG, stateToString(update)) }
+        outputListeners.forEach { it.onFoldUpdate(update) }
+        lastFoldUpdate = update
+    }
+
+    private fun rescheduleAbortAnimationTimeout() {
+        if (isTransitionInProgess) {
+            cancelTimeout()
+        }
+        handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS)
+    }
+
+    private fun cancelTimeout() {
+        handler.removeCallbacks(timeoutRunnable)
+    }
+
     private inner class ScreenStatusListener :
         ScreenStatusProvider.ScreenListener {
 
@@ -136,7 +168,39 @@
             onHingeAngle(angle)
         }
     }
+
+    private inner class TimeoutRunnable : Runnable {
+
+        override fun run() {
+            notifyFoldUpdate(FOLD_UPDATE_ABORTED)
+        }
+    }
 }
 
-private const val START_CLOSING_THRESHOLD_DEGREES = 95f
-private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
+private fun stateToString(@FoldUpdate update: Int): String {
+    return when (update) {
+        FOLD_UPDATE_START_OPENING -> "START_OPENING"
+        FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN"
+        FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
+        FOLD_UPDATE_ABORTED -> "ABORTED"
+        FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
+        FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN"
+        FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN"
+        FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
+        else -> "UNKNOWN"
+    }
+}
+
+private const val TAG = "DeviceFoldProvider"
+private const val DEBUG = false
+
+/**
+ * Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or
+ * [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
+ */
+@VisibleForTesting
+const val ABORT_CLOSING_MILLIS = 1000L
+
+/** Threshold after which we consider the device fully unfolded. */
+@VisibleForTesting
+const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 643ece3..bffebcd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -39,6 +39,7 @@
         FOLD_UPDATE_START_OPENING,
         FOLD_UPDATE_HALF_OPEN,
         FOLD_UPDATE_START_CLOSING,
+        FOLD_UPDATE_ABORTED,
         FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
         FOLD_UPDATE_FINISH_HALF_OPEN,
         FOLD_UPDATE_FINISH_FULL_OPEN,
@@ -51,7 +52,8 @@
 const val FOLD_UPDATE_START_OPENING = 0
 const val FOLD_UPDATE_HALF_OPEN = 1
 const val FOLD_UPDATE_START_CLOSING = 2
-const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3
-const val FOLD_UPDATE_FINISH_HALF_OPEN = 4
-const val FOLD_UPDATE_FINISH_FULL_OPEN = 5
-const val FOLD_UPDATE_FINISH_CLOSED = 6
+const val FOLD_UPDATE_ABORTED = 3
+const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4
+const val FOLD_UPDATE_FINISH_HALF_OPEN = 5
+const val FOLD_UPDATE_FINISH_FULL_OPEN = 6
+const val FOLD_UPDATE_FINISH_CLOSED = 7
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index e072d41..58d7dfb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -2,7 +2,6 @@
 
 import android.content.Context
 import android.os.RemoteException
-import android.util.Log
 import android.view.IRotationWatcher
 import android.view.IWindowManager
 import android.view.Surface
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
new file mode 100644
index 0000000..df9078a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -0,0 +1,50 @@
+package com.android.systemui.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+
+/** Wraps [UnfoldTransitionProgressProvider] to disable transitions when animations are disabled. */
+class ScaleAwareTransitionProgressProvider(
+    unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+    private val contentResolver: ContentResolver
+) : UnfoldTransitionProgressProvider {
+
+    private val scopedUnfoldTransitionProgressProvider =
+            ScopedUnfoldTransitionProgressProvider(unfoldTransitionProgressProvider)
+
+    private val animatorDurationScaleObserver = object : ContentObserver(null) {
+        override fun onChange(selfChange: Boolean) {
+            onAnimatorScaleChanged()
+        }
+    }
+
+    init {
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+                /* notifyForDescendants= */ false,
+                animatorDurationScaleObserver)
+        onAnimatorScaleChanged()
+    }
+
+    private fun onAnimatorScaleChanged() {
+        val animationsEnabled = ValueAnimator.areAnimatorsEnabled()
+        scopedUnfoldTransitionProgressProvider.setReadyToHandleTransition(animationsEnabled)
+    }
+
+    override fun addCallback(listener: TransitionProgressListener) {
+        scopedUnfoldTransitionProgressProvider.addCallback(listener)
+    }
+
+    override fun removeCallback(listener: TransitionProgressListener) {
+        scopedUnfoldTransitionProgressProvider.removeCallback(listener)
+    }
+
+    override fun destroy() {
+        contentResolver.unregisterContentObserver(animatorDurationScaleObserver)
+        scopedUnfoldTransitionProgressProvider.destroy()
+    }
+}
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 10ceee9..adfc872 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,7 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import java.util.function.Supplier
 
 @Module(includes = [
     SettingsUtilModule::class
@@ -38,5 +39,9 @@
         fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
             return FlagManager(context, handler)
         }
+
+        @JvmStatic
+        @Provides
+        fun providesFlagCollector(): Supplier<Map<Int, Flag<*>>>? = null
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index ac463eb..1571913 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -168,6 +168,12 @@
         if (!mIsDozing) mView.animateAppearOnLockscreen();
     }
 
+    /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
+     * fully folded state and it goes to sleep (always on display screen) */
+    public void animateFoldAppear() {
+        mView.animateFoldAppear();
+    }
+
     /**
      * Updates the time for the view.
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
deleted file mode 100644
index 2a0c285..0000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.java
+++ /dev/null
@@ -1,315 +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.keyguard;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import java.util.Calendar;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import kotlin.Unit;
-
-/**
- * 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.
- */
-public class AnimatableClockView extends TextView {
-    private static final CharSequence DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm";
-    private static final CharSequence DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm";
-    private static final long DOZE_ANIM_DURATION = 300;
-    private static final long APPEAR_ANIM_DURATION = 350;
-    private static final long CHARGE_ANIM_DURATION_PHASE_0 = 500;
-    private static final long CHARGE_ANIM_DURATION_PHASE_1 = 1000;
-
-    private final Calendar mTime = Calendar.getInstance();
-
-    private final int mDozingWeight;
-    private final int mLockScreenWeight;
-    private CharSequence mFormat;
-    private CharSequence mDescFormat;
-    private int mDozingColor;
-    private int mLockScreenColor;
-    private float mLineSpacingScale = 1f;
-    private int mChargeAnimationDelay = 0;
-
-    private TextAnimator mTextAnimator = null;
-    private Runnable mOnTextAnimatorInitialized;
-
-    private boolean mIsSingleLine;
-
-    public AnimatableClockView(Context context) {
-        this(context, null, 0, 0);
-    }
-
-    public AnimatableClockView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0, 0);
-    }
-
-    public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public AnimatableClockView(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        TypedArray ta = context.obtainStyledAttributes(
-                attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes);
-        try {
-            mDozingWeight = ta.getInt(R.styleable.AnimatableClockView_dozeWeight, 100);
-            mLockScreenWeight = ta.getInt(R.styleable.AnimatableClockView_lockScreenWeight, 300);
-            mChargeAnimationDelay = ta.getInt(
-                    R.styleable.AnimatableClockView_chargeAnimationDelay, 200);
-        } finally {
-            ta.recycle();
-        }
-
-        ta = context.obtainStyledAttributes(
-                attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
-        try {
-            mIsSingleLine = ta.getBoolean(android.R.styleable.TextView_singleLine, false);
-        } finally {
-            ta.recycle();
-        }
-
-        refreshFormat();
-    }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        refreshFormat();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-    }
-
-    int getDozingWeight() {
-        if (useBoldedVersion()) {
-            return mDozingWeight + 100;
-        }
-        return mDozingWeight;
-    }
-
-    int getLockScreenWeight() {
-        if (useBoldedVersion()) {
-            return mLockScreenWeight + 100;
-        }
-        return mLockScreenWeight;
-    }
-
-    /**
-     * Whether to use a bolded version based on the user specified fontWeightAdjustment.
-     */
-    boolean useBoldedVersion() {
-        // "Bold text" fontWeightAdjustment is 300.
-        return getResources().getConfiguration().fontWeightAdjustment > 100;
-    }
-
-    void refreshTime() {
-        mTime.setTimeInMillis(System.currentTimeMillis());
-        setText(DateFormat.format(mFormat, mTime));
-        setContentDescription(DateFormat.format(mDescFormat, mTime));
-    }
-
-    void onTimeZoneChanged(TimeZone timeZone) {
-        mTime.setTimeZone(timeZone);
-        refreshFormat();
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        if (mTextAnimator == null) {
-            mTextAnimator = new TextAnimator(
-                    getLayout(),
-                    () -> {
-                        invalidate();
-                        return Unit.INSTANCE;
-                    });
-            if (mOnTextAnimatorInitialized != null) {
-                mOnTextAnimatorInitialized.run();
-                mOnTextAnimatorInitialized = null;
-            }
-        } else {
-            mTextAnimator.updateLayout(getLayout());
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        mTextAnimator.draw(canvas);
-    }
-
-    void setLineSpacingScale(float scale) {
-        mLineSpacingScale = scale;
-        setLineSpacing(0, mLineSpacingScale);
-    }
-
-    void setColors(int dozingColor, int lockScreenColor) {
-        mDozingColor = dozingColor;
-        mLockScreenColor = lockScreenColor;
-    }
-
-    void animateAppearOnLockscreen() {
-        if (mTextAnimator == null) {
-            return;
-        }
-
-        setTextStyle(
-                getDozingWeight(),
-                -1 /* text size, no update */,
-                mLockScreenColor,
-                false /* animate */,
-                0 /* duration */,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-
-        setTextStyle(
-                getLockScreenWeight(),
-                -1 /* text size, no update */,
-                mLockScreenColor,
-                true, /* animate */
-                APPEAR_ANIM_DURATION,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-    }
-
-    void animateCharge(DozeStateGetter dozeStateGetter) {
-        if (mTextAnimator == null || mTextAnimator.isRunning()) {
-            // Skip charge animation if dozing animation is already playing.
-            return;
-        }
-        Runnable startAnimPhase2 = () -> setTextStyle(
-                dozeStateGetter.isDozing() ? getDozingWeight() : getLockScreenWeight() /* weight */,
-                -1,
-                null,
-                true /* animate */,
-                CHARGE_ANIM_DURATION_PHASE_1,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-        setTextStyle(dozeStateGetter.isDozing()
-                        ? getLockScreenWeight()
-                        : getDozingWeight()/* weight */,
-                -1,
-                null,
-                true /* animate */,
-                CHARGE_ANIM_DURATION_PHASE_0,
-                mChargeAnimationDelay,
-                startAnimPhase2);
-    }
-
-    void animateDoze(boolean isDozing, boolean animate) {
-        setTextStyle(isDozing ? getDozingWeight() : getLockScreenWeight() /* weight */,
-                -1,
-                isDozing ? mDozingColor : mLockScreenColor,
-                animate,
-                DOZE_ANIM_DURATION,
-                0 /* delay */,
-                null /* onAnimationEnd */);
-    }
-
-    /**
-     * 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 void setTextStyle(
-            @IntRange(from = 0, to = 1000) int weight,
-            @FloatRange(from = 0) float textSize,
-            Integer color,
-            boolean animate,
-            long duration,
-            long delay,
-            Runnable onAnimationEnd) {
-        if (mTextAnimator != null) {
-            mTextAnimator.setTextStyle(weight, textSize, color, animate, duration, null,
-                    delay, onAnimationEnd);
-        } else {
-            // when the text animator is set, update its start values
-            mOnTextAnimatorInitialized =
-                    () -> mTextAnimator.setTextStyle(
-                            weight, textSize, color, false, duration, null,
-                            delay, onAnimationEnd);
-        }
-    }
-
-    void refreshFormat() {
-        Patterns.update(mContext);
-
-        final boolean use24HourFormat = DateFormat.is24HourFormat(getContext());
-        if (mIsSingleLine && use24HourFormat) {
-            mFormat = Patterns.sClockView24;
-        } else if (!mIsSingleLine && use24HourFormat) {
-            mFormat = DOUBLE_LINE_FORMAT_24_HOUR;
-        } else if (mIsSingleLine && !use24HourFormat) {
-            mFormat = Patterns.sClockView12;
-        } else {
-            mFormat = DOUBLE_LINE_FORMAT_12_HOUR;
-        }
-
-        mDescFormat = use24HourFormat ? Patterns.sClockView24 : Patterns.sClockView12;
-        refreshTime();
-    }
-
-    // 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 static final class Patterns {
-        static String sClockView12;
-        static String sClockView24;
-        static String sCacheKey;
-
-        static void update(Context context) {
-            final Locale locale = Locale.getDefault();
-            final Resources res = context.getResources();
-            final String clockView12Skel = res.getString(R.string.clock_12hr_format);
-            final String clockView24Skel = res.getString(R.string.clock_24hr_format);
-            final String key = locale.toString() + clockView12Skel + clockView24Skel;
-            if (key.equals(sCacheKey)) return;
-            sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
-
-            // 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 = sClockView12.replaceAll("a", "").trim();
-            }
-            sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
-            sCacheKey = key;
-        }
-    }
-
-    interface DozeStateGetter {
-        boolean isDozing();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
new file mode 100644
index 0000000..357be25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard
+
+import 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.text.format.DateFormat
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import java.util.Calendar
+import java.util.Locale
+import java.util.TimeZone
+
+/**
+ * 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.
+ */
+class AnimatableClockView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+
+    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
+
+    val dozingWeight: Int
+        get() = if (useBoldedVersion()) dozingWeightInternal + 100 else dozingWeightInternal
+
+    val lockScreenWeight: Int
+        get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+
+    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()
+        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 = System.currentTimeMillis()
+        text = DateFormat.format(format, time)
+        contentDescription = DateFormat.format(descFormat, time)
+    }
+
+    fun onTimeZoneChanged(timeZone: TimeZone?) {
+        time.timeZone = timeZone
+        refreshFormat()
+    }
+
+    @SuppressLint("DrawAllocation")
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        val animator = textAnimator
+        if (animator == null) {
+            textAnimator = TextAnimator(layout) { invalidate() }
+            onTextAnimatorInitialized?.run()
+            onTextAnimatorInitialized = null
+        } else {
+            animator.updateLayout(layout)
+        }
+    }
+
+    override fun onDraw(canvas: Canvas) {
+        textAnimator?.draw(canvas)
+    }
+
+    fun setLineSpacingScale(scale: Float) {
+        lineSpacingScale = scale
+        setLineSpacing(0f, lineSpacingScale)
+    }
+
+    fun setColors(@ColorInt dozingColor: Int, lockScreenColor: Int) {
+        this.dozingColor = dozingColor
+        this.lockScreenColor = lockScreenColor
+    }
+
+    fun animateAppearOnLockscreen() {
+        if (textAnimator == null) {
+            return
+        }
+        setTextStyle(
+            weight = dozingWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = lockScreenWeight,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = true,
+            duration = APPEAR_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateFoldAppear() {
+        if (textAnimator == null) {
+            return
+        }
+        setTextStyle(
+            weight = lockScreenWeightInternal,
+            textSize = -1f,
+            color = lockScreenColor,
+            animate = false,
+            duration = 0,
+            delay = 0,
+            onAnimationEnd = null
+        )
+        setTextStyle(
+            weight = dozingWeightInternal,
+            textSize = -1f,
+            color = dozingColor,
+            animate = true,
+            interpolator = Interpolators.EMPHASIZED_DECELERATE,
+            duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    fun animateCharge(dozeStateGetter: DozeStateGetter) {
+        if (textAnimator == null || textAnimator!!.isRunning()) {
+            // Skip charge animation if dozing animation is already playing.
+            return
+        }
+        val startAnimPhase2 = Runnable {
+            setTextStyle(
+                weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight,
+                textSize = -1f,
+                color = null,
+                animate = true,
+                duration = CHARGE_ANIM_DURATION_PHASE_1,
+                delay = 0,
+                onAnimationEnd = null
+            )
+        }
+        setTextStyle(
+            weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight,
+            textSize = -1f,
+            color = null,
+            animate = true,
+            duration = CHARGE_ANIM_DURATION_PHASE_0,
+            delay = chargeAnimationDelay.toLong(),
+            onAnimationEnd = startAnimPhase2
+        )
+    }
+
+    fun animateDoze(isDozing: Boolean, animate: Boolean) {
+        setTextStyle(
+            weight = if (isDozing) dozingWeight else lockScreenWeight,
+            textSize = -1f,
+            color = if (isDozing) dozingColor else lockScreenColor,
+            animate = animate,
+            duration = DOZE_ANIM_DURATION,
+            delay = 0,
+            onAnimationEnd = null
+        )
+    }
+
+    /**
+     * 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,
+                duration = duration,
+                interpolator = interpolator,
+                delay = delay,
+                onAnimationEnd = onAnimationEnd
+            )
+        } 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
+                )
+            }
+        }
+    }
+
+    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,
+            interpolator = null,
+            duration = duration,
+            delay = delay,
+            onAnimationEnd = onAnimationEnd
+        )
+    }
+
+    fun refreshFormat() {
+        Patterns.update(context)
+        val use24HourFormat = DateFormat.is24HourFormat(context)
+
+        format = when {
+            isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
+            !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR
+            isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
+            else -> DOUBLE_LINE_FORMAT_12_HOUR
+        }
+
+        descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
+
+        refreshTime()
+    }
+
+    // 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
+        }
+    }
+
+    interface DozeStateGetter {
+        val isDozing: Boolean
+    }
+}
+
+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
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 9238b82..25dcdf9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -190,11 +190,15 @@
         }
     }
 
-    private void animateClockChange(boolean useLargeClock) {
+    private void updateClockViews(boolean useLargeClock, boolean animate) {
         if (mClockInAnim != null) mClockInAnim.cancel();
         if (mClockOutAnim != null) mClockOutAnim.cancel();
         if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
 
+        mClockInAnim = null;
+        mClockOutAnim = null;
+        mStatusAreaAnim = null;
+
         View in, out;
         int direction = 1;
         float statusAreaYTranslation;
@@ -214,6 +218,14 @@
             removeView(out);
         }
 
+        if (!animate) {
+            out.setAlpha(0f);
+            in.setAlpha(1f);
+            in.setVisibility(VISIBLE);
+            mStatusArea.setTranslationY(statusAreaYTranslation);
+            return;
+        }
+
         mClockOutAnim = new AnimatorSet();
         mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
         mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
@@ -273,7 +285,7 @@
      *
      * @return true if desired clock appeared and false if it was already visible
      */
-    boolean switchToClock(@ClockSize int clockSize) {
+    boolean switchToClock(@ClockSize int clockSize, boolean animate) {
         if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
             return false;
         }
@@ -281,7 +293,7 @@
         // let's make sure clock is changed only after all views were laid out so we can
         // translate them properly
         if (mChildrenAreLaidOut) {
-            animateClockChange(clockSize == LARGE);
+            updateClockViews(clockSize == LARGE, animate);
         }
 
         mDisplayedClockSize = clockSize;
@@ -293,7 +305,7 @@
         super.onLayout(changed, l, t, r, b);
 
         if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
-            animateClockChange(mDisplayedClockSize == LARGE);
+            updateClockViews(mDisplayedClockSize == LARGE, /* animate */ true);
         }
 
         mChildrenAreLaidOut = true;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index c628d44..25b5511 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -89,7 +89,6 @@
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final KeyguardBypassController mBypassController;
 
-    private int mLargeClockTopMargin = 0;
     private int mKeyguardClockTopMargin = 0;
 
     /**
@@ -276,33 +275,37 @@
     }
 
     private void updateClockLayout() {
-        if (mSmartspaceController.isEnabled()) {
-            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
-                    MATCH_PARENT);
-            mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize(
-                    R.dimen.keyguard_large_clock_top_margin);
-            lp.topMargin = mLargeClockTopMargin;
-            mLargeClockFrame.setLayoutParams(lp);
-        } else {
-            mLargeClockTopMargin = 0;
-        }
+        int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
+                R.dimen.keyguard_large_clock_top_margin);
+
+        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
+                MATCH_PARENT);
+        lp.topMargin = largeClockTopMargin;
+        mLargeClockFrame.setLayoutParams(lp);
     }
 
     /**
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
      */
-    public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize) {
+    public void displayClock(@KeyguardClockSwitch.ClockSize int clockSize, boolean animate) {
         if (!mCanShowDoubleLineClock && clockSize == KeyguardClockSwitch.LARGE) {
             return;
         }
 
-        boolean appeared = mView.switchToClock(clockSize);
-        if (appeared && clockSize == LARGE) {
+        boolean appeared = mView.switchToClock(clockSize, animate);
+        if (animate && appeared && clockSize == LARGE) {
             mLargeClockViewController.animateAppear();
         }
     }
 
+    public void animateFoldToAod() {
+        if (mClockViewController != null) {
+            mClockViewController.animateFoldAppear();
+            mLargeClockViewController.animateFoldAppear();
+        }
+    }
+
     /**
      * If we're presenting a custom clock of just the default one.
      */
@@ -358,8 +361,6 @@
                 mKeyguardUnlockAnimationController.updateLockscreenSmartSpacePosition();
             }
         }
-
-        mKeyguardSliceViewController.updatePosition(x, props, animate);
     }
 
     /** Sets an alpha value on every child view except for the smartspace. */
@@ -445,7 +446,7 @@
             Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, 1) != 0;
 
         if (!mCanShowDoubleLineClock) {
-            mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL));
+            mUiExecutor.execute(() -> displayClock(KeyguardClockSwitch.SMALL, /* animate */ true));
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index 40190c1..7eae729 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -48,10 +48,6 @@
 
     abstract CharSequence getTitle();
 
-    void animateForIme(float interpolatedFraction, boolean appearingAnim) {
-        return;
-    }
-
     boolean disallowInterceptTouch(MotionEvent event) {
         return false;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 3a3d308..bc366ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -156,8 +156,7 @@
         setAlpha(0f);
         animate()
             .alpha(1f)
-            .setDuration(500)
-            .setStartDelay(300)
+            .setDuration(300)
             .start();
 
         setTranslationY(0f);
@@ -219,15 +218,6 @@
         return true;
     }
 
-
-    @Override
-    public void animateForIme(float interpolatedFraction, boolean appearingAnim) {
-        animate().cancel();
-        setAlpha(appearingAnim
-                ? Math.max(interpolatedFraction, getAlpha())
-                : 1 - interpolatedFraction);
-    }
-
     @Override
     public CharSequence getTitle() {
         return getResources().getString(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 172c7f6..b84cb19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -19,21 +19,29 @@
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
+import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
+
 import static java.lang.Integer.max;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
 import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
 import android.util.TypedValue;
 import android.view.Gravity;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -44,7 +52,10 @@
 import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
+import android.widget.AdapterView;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -56,12 +67,17 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.UserIcons;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherController.BaseUserAdapter;
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import java.util.ArrayList;
@@ -110,6 +126,8 @@
     @VisibleForTesting
     KeyguardSecurityViewFlipper mSecurityViewFlipper;
     private GlobalSettings mGlobalSettings;
+    private FalsingManager mFalsingManager;
+    private UserSwitcherController mUserSwitcherController;
     private AlertDialog mAlertDialog;
     private boolean mSwipeUpToRetry;
 
@@ -124,7 +142,7 @@
     private float mStartTouchY = -1;
     private boolean mDisappearAnimRunning;
     private SwipeListener mSwipeListener;
-    private ModeLogic mModeLogic = new DefaultModeLogic();
+    private ViewMode mViewMode = new DefaultViewMode();
     private @Mode int mCurrentMode = MODE_DEFAULT;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -173,8 +191,11 @@
                                 interpolatedFraction);
                         translationY += paddingBottom;
                     }
-                    mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction,
-                            !mDisappearAnimRunning);
+
+                    float alpha = mDisappearAnimRunning
+                            ? 1 - interpolatedFraction
+                            : Math.max(interpolatedFraction, getAlpha());
+                    updateChildren(translationY, alpha);
 
                     return windowInsets;
                 }
@@ -183,12 +204,19 @@
                 public void onEnd(WindowInsetsAnimation animation) {
                     if (!mDisappearAnimRunning) {
                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
-                        mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f,
-                                true /* appearingAnim */);
+                        updateChildren(0 /* translationY */, 1f /* alpha */);
                     } else {
                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
                     }
                 }
+
+                private void updateChildren(int translationY, float alpha) {
+                    for (int i = 0; i < KeyguardSecurityContainer.this.getChildCount(); ++i) {
+                        View child = KeyguardSecurityContainer.this.getChildAt(i);
+                        child.setTranslationY(translationY);
+                        child.setAlpha(alpha);
+                    }
+                }
             };
 
     // Used to notify the container when something interesting happens.
@@ -270,9 +298,12 @@
     void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
         updateBiometricRetry(securityMode, faceAuthEnabled);
+
+        setupViewMode();
     }
 
-    void initMode(@Mode int mode, GlobalSettings globalSettings) {
+    void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
+            UserSwitcherController userSwitcherController) {
         if (mCurrentMode == mode) return;
         Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
                 + modeToString(mode));
@@ -280,16 +311,18 @@
 
         switch (mode) {
             case MODE_ONE_HANDED:
-                mModeLogic = new OneHandedModeLogic();
+                mViewMode = new OneHandedViewMode();
                 break;
             case MODE_USER_SWITCHER:
-                mModeLogic = new UserSwitcherModeLogic();
+                mViewMode = new UserSwitcherViewMode();
                 break;
             default:
-                mModeLogic = new DefaultModeLogic();
+                mViewMode = new DefaultViewMode();
         }
         mGlobalSettings = globalSettings;
-        finishSetup();
+        mFalsingManager = falsingManager;
+        mUserSwitcherController = userSwitcherController;
+        setupViewMode();
     }
 
     private String modeToString(@Mode int mode) {
@@ -305,10 +338,14 @@
         }
     }
 
-    private void finishSetup() {
-        if (mSecurityViewFlipper == null || mGlobalSettings == null) return;
+    private void setupViewMode() {
+        if (mSecurityViewFlipper == null || mGlobalSettings == null
+                || mFalsingManager == null || mUserSwitcherController == null) {
+            return;
+        }
 
-        mModeLogic.init(this, mGlobalSettings, mSecurityViewFlipper);
+        mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager,
+                mUserSwitcherController);
     }
 
     @Mode int getMode() {
@@ -321,13 +358,13 @@
      * that the user last interacted with.
      */
     void updatePositionByTouchX(float x) {
-        mModeLogic.updatePositionByTouchX(x);
+        mViewMode.updatePositionByTouchX(x);
     }
 
     /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
     public boolean isOneHandedModeLeftAligned() {
         return mCurrentMode == MODE_ONE_HANDED
-                && ((OneHandedModeLogic) mModeLogic).isLeftAligned();
+                && ((OneHandedViewMode) mViewMode).isLeftAligned();
     }
 
     public void onPause() {
@@ -336,6 +373,7 @@
             mAlertDialog = null;
         }
         mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
+        mViewMode.reset();
     }
 
     @Override
@@ -428,7 +466,7 @@
                 }
             } else {
                 if (!mIsDragging) {
-                    mModeLogic.handleTap(event);
+                    mViewMode.handleTap(event);
                 }
             }
         }
@@ -453,8 +491,19 @@
                 .animateToFinalPosition(0);
     }
 
+    /**
+     * Runs after a succsssful authentication only
+     */
     public void startDisappearAnimation(SecurityMode securitySelection) {
         mDisappearAnimRunning = true;
+        mViewMode.startDisappearAnimation(securitySelection);
+    }
+
+    /**
+     * This will run when the bouncer shows in all cases except when the user drags the bouncer up.
+     */
+    public void startAppearAnimation(SecurityMode securityMode) {
+        mViewMode.startAppearAnimation(securityMode);
     }
 
     private void beginJankInstrument(int cuj) {
@@ -490,8 +539,6 @@
     public void onFinishInflate() {
         super.onFinishInflate();
         mSecurityViewFlipper = findViewById(R.id.view_flipper);
-
-        finishSetup();
     }
 
     @Override
@@ -562,10 +609,7 @@
         for (int i = 0; i < getChildCount(); i++) {
             final View view = getChildAt(i);
             if (view.getVisibility() != GONE) {
-                int updatedWidthMeasureSpec = widthMeasureSpec;
-                if (view == mSecurityViewFlipper) {
-                    updatedWidthMeasureSpec = mModeLogic.getChildWidthMeasureSpec(widthMeasureSpec);
-                }
+                int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec);
                 measureChildWithMargins(view, updatedWidthMeasureSpec, 0, heightMeasureSpec, 0);
 
                 final LayoutParams lp = (LayoutParams) view.getLayoutParams();
@@ -595,7 +639,13 @@
 
         // After a layout pass, we need to re-place the inner bouncer, as our bounds may have
         // changed.
-        mModeLogic.updateSecurityViewLocation();
+        mViewMode.updateSecurityViewLocation();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration config) {
+        super.onConfigurationChanged(config);
+        mViewMode.updateSecurityViewLocation();
     }
 
     void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
@@ -643,10 +693,12 @@
     /**
      * Enscapsulates the differences between bouncer modes for the container.
      */
-    private interface ModeLogic {
+    interface ViewMode {
 
-        default void init(ViewGroup v, GlobalSettings globalSettings,
-                KeyguardSecurityViewFlipper viewFlipper) {};
+        default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+                @NonNull KeyguardSecurityViewFlipper viewFlipper,
+                @NonNull FalsingManager falsingManager,
+                @NonNull UserSwitcherController userSwitcherController) {};
 
         /** Reinitialize the location */
         default void updateSecurityViewLocation() {};
@@ -657,19 +709,33 @@
         /** A tap on the container, outside of the ViewFlipper */
         default void handleTap(MotionEvent event) {};
 
+        /** Called when the view needs to reset or hides */
+        default void reset() {};
+
+        /** On a successful auth, optionally handle how the view disappears */
+        default void startDisappearAnimation(SecurityMode securityMode) {};
+
+        /** On notif tap, this animation will run */
+        default void startAppearAnimation(SecurityMode securityMode) {};
+
         /** Override to alter the width measure spec to perhaps limit the ViewFlipper size */
         default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
             return parentWidthMeasureSpec;
         }
     }
 
-    private static class DefaultModeLogic implements ModeLogic {
+    /**
+     * Default bouncer is centered within the space
+     */
+    static class DefaultViewMode implements ViewMode {
         private ViewGroup mView;
         private KeyguardSecurityViewFlipper mViewFlipper;
 
         @Override
-        public void init(ViewGroup v, GlobalSettings globalSettings,
-                KeyguardSecurityViewFlipper viewFlipper) {
+        public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+                @NonNull KeyguardSecurityViewFlipper viewFlipper,
+                @NonNull FalsingManager falsingManager,
+                @NonNull UserSwitcherController userSwitcherController) {
             mView = v;
             mViewFlipper = viewFlipper;
 
@@ -682,7 +748,6 @@
                     (FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
             lp.gravity = Gravity.CENTER_HORIZONTAL;
             mViewFlipper.setLayoutParams(lp);
-
             mViewFlipper.setTranslationX(0);
         }
     }
@@ -691,13 +756,172 @@
      * User switcher mode will display both the current user icon as well as
      * a user switcher, in both portrait and landscape modes.
      */
-    private static class UserSwitcherModeLogic implements ModeLogic {
+    static class UserSwitcherViewMode implements ViewMode {
         private ViewGroup mView;
+        private ViewGroup mUserSwitcherViewGroup;
+        private KeyguardSecurityViewFlipper mViewFlipper;
+        private ImageView mUserIconView;
+        private TextView mUserSwitcher;
+        private FalsingManager mFalsingManager;
+        private UserSwitcherController mUserSwitcherController;
+        private KeyguardUserSwitcherPopupMenu mPopup;
 
         @Override
-        public void init(ViewGroup v, GlobalSettings globalSettings,
-                KeyguardSecurityViewFlipper viewFlipper) {
+        public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
+                @NonNull KeyguardSecurityViewFlipper viewFlipper,
+                @NonNull FalsingManager falsingManager,
+                @NonNull UserSwitcherController userSwitcherController) {
             mView = v;
+            mViewFlipper = viewFlipper;
+            mFalsingManager = falsingManager;
+            mUserSwitcherController = userSwitcherController;
+
+            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);
+            }
+
+            mUserIconView = mView.findViewById(R.id.user_icon);
+            Drawable icon = UserIcons.getDefaultUserIcon(v.getContext().getResources(), 0, false);
+            mUserIconView.setImageDrawable(icon);
+
+            updateSecurityViewLocation();
+
+            mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
+            setupUserSwitcher();
+        }
+
+        @Override
+        public void reset() {
+            if (mPopup != null) {
+                mPopup.dismiss();
+                mPopup = null;
+            }
+        }
+
+        @Override
+        public void startAppearAnimation(SecurityMode securityMode) {
+            // IME insets animations handle alpha and translation
+            if (securityMode == SecurityMode.Password) {
+                return;
+            }
+
+            mUserSwitcherViewGroup.setAlpha(0f);
+            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
+                    1f);
+            alphaAnim.setInterpolator(Interpolators.ALPHA_IN);
+            alphaAnim.setDuration(500);
+            alphaAnim.start();
+        }
+
+        @Override
+        public void startDisappearAnimation(SecurityMode securityMode) {
+            // IME insets animations handle alpha and translation
+            if (securityMode == SecurityMode.Password) {
+                return;
+            }
+
+            int yTranslation = mView.getContext().getResources().getDimensionPixelSize(
+                    R.dimen.disappear_y_translation);
+
+            AnimatorSet anims = new AnimatorSet();
+            ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
+            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f);
+
+            anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+            anims.playTogether(alphaAnim, yAnim);
+            anims.start();
+        }
+
+        private void setupUserSwitcher() {
+            String currentUserName = mUserSwitcherController.getCurrentUserName();
+            mUserSwitcher.setText(currentUserName);
+
+            ViewGroup anchor = mView.findViewById(R.id.user_switcher_anchor);
+            BaseUserAdapter adapter = new BaseUserAdapter(mUserSwitcherController) {
+                @Override
+                public View getView(int position, View convertView, ViewGroup parent) {
+                    UserRecord item = getItem(position);
+                    TextView view = (TextView) convertView;
+                    if (view == null) {
+                        view = (TextView) LayoutInflater.from(parent.getContext()).inflate(
+                                R.layout.keyguard_bouncer_user_switcher_item,
+                                parent,
+                                false);
+                    }
+                    view.setText(getName(parent.getContext(), item));
+                    return view;
+                }
+            };
+
+            if (adapter.getCount() < 2) {
+                // The drop down arrow is at index 1
+                ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(0);
+                anchor.setClickable(false);
+                return;
+            } else {
+                ((LayerDrawable) mUserSwitcher.getBackground()).getDrawable(1).setAlpha(255);
+            }
+
+            anchor.setOnClickListener((v) -> {
+                if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
+
+                mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
+                mPopup.setAnchorView(anchor);
+                mPopup.setAdapter(adapter);
+                mPopup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                        public void onItemClick(AdapterView parent, View view, int pos, long id) {
+                            if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
+
+                            UserRecord user = adapter.getItem(pos);
+                            if (!user.isCurrent) {
+                                adapter.onUserListItemClicked(user);
+                            }
+                            mPopup.dismiss();
+                            mPopup = null;
+                        }
+                    });
+                mPopup.show();
+            });
+        }
+
+        /**
+         * Each view will get half the width. Yes, it would be easier to use something other than
+         * FrameLayout but it was too disruptive to downstream projects to change.
+         */
+        @Override
+        public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
+            return MeasureSpec.makeMeasureSpec(
+                    MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
+                    MeasureSpec.getMode(parentWidthMeasureSpec));
+        }
+
+        @Override
+        public void updateSecurityViewLocation() {
+            if (mView.getContext().getResources().getConfiguration().orientation
+                    == Configuration.ORIENTATION_PORTRAIT) {
+                updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
+                updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
+                mUserSwitcherViewGroup.setTranslationY(0);
+            } else {
+                updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM);
+                updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
+
+                // Attempt to reposition a bit higher to make up for this frame being a bit lower
+                // on the device
+                int yTrans = mView.getContext().getResources().getDimensionPixelSize(
+                        R.dimen.status_bar_height);
+                mUserSwitcherViewGroup.setTranslationY(-yTrans);
+            }
+        }
+
+        private void updateViewGravity(View v, int gravity) {
+            FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+            lp.gravity = gravity;
+            v.setLayoutParams(lp);
         }
     }
 
@@ -705,7 +929,7 @@
      * Logic to enabled one-handed bouncer mode. Supports animating the bouncer
      * between alternate sides of the display.
      */
-    private static class OneHandedModeLogic implements ModeLogic {
+    static class OneHandedViewMode implements ViewMode {
         @Nullable private ValueAnimator mRunningOneHandedAnimator;
         private ViewGroup mView;
         private KeyguardSecurityViewFlipper mViewFlipper;
@@ -713,7 +937,9 @@
 
         @Override
         public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
-                @NonNull KeyguardSecurityViewFlipper viewFlipper) {
+                @NonNull KeyguardSecurityViewFlipper viewFlipper,
+                @NonNull FalsingManager falsingManager,
+                @NonNull UserSwitcherController userSwitcherController) {
             mView = v;
             mViewFlipper = viewFlipper;
             mGlobalSettings = globalSettings;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 4035229..2fb2211 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -51,9 +51,11 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
 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.ViewController;
 import com.android.systemui.util.settings.GlobalSettings;
 
@@ -78,6 +80,8 @@
     private final SecurityCallback mSecurityCallback;
     private final ConfigurationController mConfigurationController;
     private final FalsingCollector mFalsingCollector;
+    private final FalsingManager mFalsingManager;
+    private final UserSwitcherController mUserSwitcherController;
     private final GlobalSettings mGlobalSettings;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -232,6 +236,8 @@
             KeyguardSecurityViewFlipperController securityViewFlipperController,
             ConfigurationController configurationController,
             FalsingCollector falsingCollector,
+            FalsingManager falsingManager,
+            UserSwitcherController userSwitcherController,
             GlobalSettings globalSettings) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
@@ -247,6 +253,8 @@
         mConfigurationController = configurationController;
         mLastOrientation = getResources().getConfiguration().orientation;
         mFalsingCollector = falsingCollector;
+        mFalsingManager = falsingManager;
+        mUserSwitcherController = userSwitcherController;
         mGlobalSettings = globalSettings;
     }
 
@@ -343,14 +351,14 @@
 
     public void startAppearAnimation() {
         if (mCurrentSecurityMode != SecurityMode.None) {
+            mView.startAppearAnimation(mCurrentSecurityMode);
             getCurrentSecurityController().startAppearAnimation();
         }
     }
 
     public boolean startDisappearAnimation(Runnable onFinishRunnable) {
-        mView.startDisappearAnimation(getCurrentSecurityMode());
-
         if (mCurrentSecurityMode != SecurityMode.None) {
+            mView.startDisappearAnimation(mCurrentSecurityMode);
             return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable);
         }
 
@@ -506,15 +514,16 @@
     }
 
     private void configureMode() {
-        // One-handed mode and user-switcher are currently mutually exclusive, and enforced here
+        boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin
+                || mCurrentSecurityMode == SecurityMode.SimPuk;
         int mode = KeyguardSecurityContainer.MODE_DEFAULT;
-        if (canDisplayUserSwitcher()) {
+        if (canDisplayUserSwitcher() && !useSimSecurity) {
             mode = KeyguardSecurityContainer.MODE_USER_SWITCHER;
         } else if (canUseOneHandedBouncer()) {
             mode = KeyguardSecurityContainer.MODE_ONE_HANDED;
         }
 
-        mView.initMode(mode, mGlobalSettings);
+        mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController);
     }
 
     public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
@@ -604,7 +613,9 @@
         private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
         private final ConfigurationController mConfigurationController;
         private final FalsingCollector mFalsingCollector;
+        private final FalsingManager mFalsingManager;
         private final GlobalSettings mGlobalSettings;
+        private final UserSwitcherController mUserSwitcherController;
 
         @Inject
         Factory(KeyguardSecurityContainer view,
@@ -619,6 +630,8 @@
                 KeyguardSecurityViewFlipperController securityViewFlipperController,
                 ConfigurationController configurationController,
                 FalsingCollector falsingCollector,
+                FalsingManager falsingManager,
+                UserSwitcherController userSwitcherController,
                 GlobalSettings globalSettings) {
             mView = view;
             mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
@@ -631,7 +644,9 @@
             mSecurityViewFlipperController = securityViewFlipperController;
             mConfigurationController = configurationController;
             mFalsingCollector = falsingCollector;
+            mFalsingManager = falsingManager;
             mGlobalSettings = globalSettings;
+            mUserSwitcherController = userSwitcherController;
         }
 
         public KeyguardSecurityContainerController create(
@@ -640,7 +655,8 @@
                     mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
                     mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                     mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
-                    mConfigurationController, mFalsingCollector, mGlobalSettings);
+                    mConfigurationController, mFalsingCollector, mFalsingManager,
+                    mUserSwitcherController, mGlobalSettings);
         }
 
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index e01e17d..4d2391a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -83,16 +83,6 @@
         return "";
     }
 
-    /**
-      * Translate the entire view, and optionally inform the wrapped view of the progress
-      * so it can animate with the parent.
-      */
-    public void animateForIme(int translationY, float interpolatedFraction, boolean appearingAnim) {
-        super.setTranslationY(translationY);
-        KeyguardInputView v = getSecurityView();
-        if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
-    }
-
     @Override
     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
         return p instanceof LayoutParams;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 0d72c93..03b647b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -92,11 +92,6 @@
     }
 
     @Override
-    public boolean startDisappearAnimation(Runnable finishRunnable) {
-        return false;
-    }
-
-    @Override
     public CharSequence getTitle() {
         return getContext().getString(
                 com.android.internal.R.string.keyguard_accessibility_sim_puk_unlock);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index d05cc4e..2af9244 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -43,9 +43,6 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
@@ -203,13 +200,6 @@
         Trace.endSection();
     }
 
-    /**
-     * Update position of the view, with optional animation
-     */
-    void updatePosition(int x, AnimationProperties props, boolean animate) {
-        PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, x, props, animate);
-    }
-
     void showSlice(Slice slice) {
         Trace.beginSection("KeyguardSliceViewController#showSlice");
         if (slice == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 60af66ba..8bf890d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -28,7 +28,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
@@ -70,7 +70,7 @@
             DozeParameters dozeParameters,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             SmartspaceTransitionController smartspaceTransitionController,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+            ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -79,7 +79,7 @@
         mDozeParameters = dozeParameters;
         mKeyguardStateController = keyguardStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, communalStateController,
-                keyguardStateController, dozeParameters, unlockedScreenOffAnimationController,
+                keyguardStateController, dozeParameters, screenOffAnimationController,
                 /* animateYPos= */ true, /* visibleOnCommunal= */ false);
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mSmartspaceTransitionController = smartspaceTransitionController;
@@ -123,8 +123,17 @@
      * Set which clock should be displayed on the keyguard. The other one will be automatically
      * hidden.
      */
-    public void displayClock(@ClockSize int clockSize) {
-        mKeyguardClockSwitchController.displayClock(clockSize);
+    public void displayClock(@ClockSize int clockSize, boolean animate) {
+        mKeyguardClockSwitchController.displayClock(clockSize, animate);
+    }
+
+    /**
+     * Performs fold to aod animation of the clocks (changes font weight from bold to thin).
+     * This animation is played when AOD is enabled and foldable device is fully folded, it is
+     * displayed on the outer screen
+     */
+    public void animateFoldToAod() {
+        mKeyguardClockSwitchController.animateFoldToAod();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ba67716..2789e27 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -51,6 +51,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -335,6 +336,8 @@
     private boolean mLockIconPressed;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final Executor mBackgroundExecutor;
+    private SensorPrivacyManager mSensorPrivacyManager;
+    private int mFaceAuthUserId;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -827,7 +830,9 @@
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged |= !mFingerprintLockedOutPermanent;
             mFingerprintLockedOutPermanent = true;
-            requireStrongAuthIfAllLockedOut();
+            Log.d(TAG, "Fingerprint locked out - requiring strong auth");
+            mLockPatternUtils.requireStrongAuth(
+                    STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, getCurrentUser());
         }
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT
@@ -837,6 +842,7 @@
             if (isUdfpsEnrolled()) {
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             }
+            stopListeningForFace();
         }
 
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1016,6 +1022,12 @@
 
         // Error is always the end of authentication lifecycle
         mFaceCancelSignal = null;
+        boolean cameraPrivacyEnabled = false;
+        if (mSensorPrivacyManager != null) {
+            cameraPrivacyEnabled = mSensorPrivacyManager
+                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+                    mFaceAuthUserId);
+        }
 
         if (msgId == FaceManager.FACE_ERROR_CANCELED
                 && mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1025,7 +1037,9 @@
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         }
 
-        if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE
+        final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE;
+
+        if (isHwUnavailable
                 || msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
             if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
                 mHardwareFaceUnavailableRetryCount++;
@@ -1038,7 +1052,10 @@
         if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
             lockedOutStateChanged = !mFaceLockedOutPermanent;
             mFaceLockedOutPermanent = true;
-            requireStrongAuthIfAllLockedOut();
+        }
+
+        if (isHwUnavailable && cameraPrivacyEnabled) {
+            errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled);
         }
 
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1148,19 +1165,6 @@
         return faceAuthenticated;
     }
 
-    private void requireStrongAuthIfAllLockedOut() {
-        final boolean faceLock =
-                (mFaceLockedOutPermanent || !shouldListenForFace()) && !getIsFaceAuthenticated();
-        final boolean fpLock =
-                mFingerprintLockedOutPermanent || !shouldListenForFingerprint(isUdfpsEnrolled());
-
-        if (faceLock && fpLock) {
-            Log.d(TAG, "All biometrics locked out - requiring strong auth");
-            mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
-                    getCurrentUser());
-        }
-    }
-
     public boolean getUserCanSkipBouncer(int userId) {
         return getUserHasTrust(userId) || getUserUnlockedWithBiometric(userId);
     }
@@ -1816,6 +1820,7 @@
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
         dumpManager.registerDumpable(getClass().getName(), this);
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2357,6 +2362,9 @@
                 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 canBypass = mKeyguardBypassController != null
                 && mKeyguardBypassController.canBypass();
         // There's no reason to ask the HAL for authentication when the user can dismiss the
@@ -2391,7 +2399,8 @@
                 && !mKeyguardGoingAway && biometricEnabledForUser && !mLockIconPressed
                 && strongAuthAllowsScanning && mIsPrimaryUser
                 && (!mSecureCameraLaunched || mOccludingAppRequestingFace)
-                && !faceAuthenticated;
+                && !faceAuthenticated
+                && !fpLockedout;
 
         // Aggregate relevant fields for debug logging.
         if (DEBUG_FACE || DEBUG_SPEW) {
@@ -2517,6 +2526,7 @@
             // This would need to be updated for multi-sensor devices
             final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
                     && mFaceSensorProperties.get(0).supportsFaceDetection;
+            mFaceAuthUserId = userId;
             if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
                 mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
             } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
new file mode 100644
index 0000000..7b6ce3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherPopupMenu.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ListPopupWindow;
+import android.widget.ListView;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.FalsingManager;
+
+/**
+ * Custom user-switcher for use on the bouncer.
+ */
+public class KeyguardUserSwitcherPopupMenu extends ListPopupWindow {
+    private Context mContext;
+    private FalsingManager mFalsingManager;
+    private int mLastHeight = -1;
+    private View.OnLayoutChangeListener mLayoutListener = (v, l, t, r, b, ol, ot, or, ob) -> {
+        int height = -v.getMeasuredHeight() + getAnchorView().getHeight();
+        if (height != mLastHeight) {
+            mLastHeight = height;
+            setVerticalOffset(height);
+            KeyguardUserSwitcherPopupMenu.super.show();
+        }
+    };
+
+    public KeyguardUserSwitcherPopupMenu(@NonNull Context context,
+            @NonNull FalsingManager falsingManager) {
+        super(context);
+        mContext = context;
+        mFalsingManager = falsingManager;
+        Resources res = mContext.getResources();
+        setBackgroundDrawable(
+                res.getDrawable(R.drawable.keyguard_user_switcher_popup_bg, context.getTheme()));
+        setModal(true);
+        setOverlapAnchor(true);
+    }
+
+    /**
+      * Show the dialog.
+      */
+    @Override
+    public void show() {
+        // need to call show() first in order to construct the listView
+        super.show();
+        ListView listView = getListView();
+
+        // This will force the popupwindow to show upward instead of drop down
+        listView.addOnLayoutChangeListener(mLayoutListener);
+
+        listView.setOnTouchListener((v, ev) -> {
+            if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+                return mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY);
+            }
+            return false;
+        });
+    }
+
+    @Override
+    public void dismiss() {
+        getListView().removeOnLayoutChangeListener(mLayoutListener);
+        super.dismiss();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index a382b53..bb608c7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -29,7 +29,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 /**
@@ -42,7 +42,7 @@
     private final CommunalStateController mCommunalStateController;
     private final KeyguardStateController mKeyguardStateController;
     private final DozeParameters mDozeParameters;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
     private final boolean mVisibleOnCommunal;
     private boolean mAnimateYPos;
     private boolean mKeyguardViewVisibilityAnimating;
@@ -53,14 +53,14 @@
             CommunalStateController communalStateController,
             KeyguardStateController keyguardStateController,
             DozeParameters dozeParameters,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             boolean animateYPos,
             boolean visibleOnCommunal) {
         mView = view;
         mCommunalStateController = communalStateController;
         mKeyguardStateController = keyguardStateController;
         mDozeParameters = dozeParameters;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         mAnimateYPos = animateYPos;
         mVisibleOnCommunal = visibleOnCommunal;
     }
@@ -136,12 +136,12 @@
                             .setStartDelay(delay);
                 }
                 animator.start();
-            } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
+            } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
                 mKeyguardViewVisibilityAnimating = true;
 
                 // Ask the screen off animation controller to animate the keyguard visibility for us
                 // since it may need to be cancelled due to keyguard lifecycle events.
-                mUnlockedScreenOffAnimationController.animateInKeyguard(
+                mScreenOffAnimationController.animateInKeyguard(
                         mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else if (mLastOccludedState && !isOccluded) {
                 // An activity was displayed over the lock screen, and has now gone away
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index b2ecc614..0c1934c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -218,11 +218,14 @@
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
-        pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
-        pw.println("Radius in pixels: " + mRadius);
-        pw.println("topLeft= (" + getX() + ", " + getY() + ")");
-        pw.println("topLeft= (" + getX() + ", " + getY() + ")");
-        pw.println("mIconType=" + typeToString(mIconType));
-        pw.println("mAod=" + mAod);
+        pw.println("Lock Icon View Parameters:");
+        pw.println("    Center in px (x, y)= ("
+                + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
+        pw.println("    Radius in pixels: " + mRadius);
+        pw.println("    mIconType=" + typeToString(mIconType));
+        pw.println("    mAod=" + mAod);
+        pw.println("Lock Icon View actual measurements:");
+        pw.println("    topLeft= (" + getX() + ", " + getY() + ")");
+        pw.println("    width=" + getWidth() + " height=" + getHeight());
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 3ce03ed7..6626f59 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -37,11 +37,10 @@
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.util.DisplayMetrics;
+import android.util.Log;
 import android.util.MathUtils;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
-import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -67,8 +66,6 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
-import com.airbnb.lottie.LottieAnimationView;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -83,11 +80,13 @@
  */
 @StatusBarComponent.StatusBarScope
 public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
+    private static final String TAG = "LockIconViewController";
     private static final float sDefaultDensity =
             (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
     private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36);
     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
+    private static final long LONG_PRESS_TIMEOUT = 200L; // milliseconds
 
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardViewController mKeyguardViewController;
@@ -98,10 +97,8 @@
     @NonNull private final AccessibilityManager mAccessibilityManager;
     @NonNull private final ConfigurationController mConfigurationController;
     @NonNull private final DelayableExecutor mExecutor;
-    @NonNull private final LayoutInflater mLayoutInflater;
     private boolean mUdfpsEnrolled;
 
-    @Nullable private LottieAnimationView mAodFp;
     @NonNull private final AnimatedStateListDrawable mIcon;
 
     @NonNull private CharSequence mUnlockedLabel;
@@ -109,6 +106,11 @@
     @Nullable private final Vibrator mVibrator;
     @Nullable private final AuthRippleController mAuthRippleController;
 
+    // Tracks the velocity of a touch to help filter out the touches that move too fast.
+    private VelocityTracker mVelocityTracker;
+    // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
+    private int mActivePointerId = -1;
+
     private boolean mIsDozing;
     private boolean mIsBouncerShowing;
     private boolean mRunningFPS;
@@ -119,6 +121,7 @@
     private boolean mUserUnlockedWithBiometric;
     private Runnable mCancelDelayedUpdateVisibilityRunnable;
     private Runnable mOnGestureDetectedRunnable;
+    private Runnable mLongPressCancelRunnable;
 
     private boolean mUdfpsSupported;
     private float mHeightPixels;
@@ -130,7 +133,7 @@
 
     // for udfps when strong auth is required or unlocked on AOD
     private boolean mShowAodLockIcon;
-    private boolean mShowAODFpIcon;
+    private boolean mShowAodUnlockedIcon;
     private final int mMaxBurnInOffsetX;
     private final int mMaxBurnInOffsetY;
     private float mInterpolatedDarkAmount;
@@ -153,8 +156,7 @@
             @NonNull @Main DelayableExecutor executor,
             @Nullable Vibrator vibrator,
             @Nullable AuthRippleController authRippleController,
-            @NonNull @Main Resources resources,
-            @NonNull LayoutInflater inflater
+            @NonNull @Main Resources resources
     ) {
         super(view);
         mStatusBarStateController = statusBarStateController;
@@ -168,7 +170,6 @@
         mExecutor = executor;
         mVibrator = vibrator;
         mAuthRippleController = authRippleController;
-        mLayoutInflater = inflater;
 
         mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
         mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -178,7 +179,7 @@
         mView.setImageDrawable(mIcon);
         mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
         mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
-        dumpManager.registerDumpable("LockIconViewController", this);
+        dumpManager.registerDumpable(TAG, this);
     }
 
     @Override
@@ -250,11 +251,12 @@
         }
 
         boolean wasShowingUnlock = mShowUnlockIcon;
-        boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon;
+        boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
+                && !mShowAodUnlockedIcon && !mShowAodLockIcon;
         mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
                 && (!mUdfpsEnrolled || !mRunningFPS);
         mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
-        mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
+        mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
         mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
 
         final CharSequence prevContentDescription = mView.getContentDescription();
@@ -271,20 +273,11 @@
             mView.updateIcon(ICON_UNLOCK, false);
             mView.setContentDescription(mUnlockedLabel);
             mView.setVisibility(View.VISIBLE);
-        } else if (mShowAODFpIcon) {
-            // AOD fp icon is special cased as a lottie view (it updates for each burn-in offset),
-            // this state shows a transparent view
-            mView.setContentDescription(null);
-            mAodFp.setVisibility(View.VISIBLE);
-            mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
-
-            mView.updateIcon(ICON_FINGERPRINT, true); // this shows no icon
+        } else if (mShowAodUnlockedIcon) {
+            mView.updateIcon(ICON_UNLOCK, true);
+            mView.setContentDescription(mUnlockedLabel);
             mView.setVisibility(View.VISIBLE);
         } else if (mShowAodLockIcon) {
-            if (wasShowingUnlock) {
-                // transition to the unlock icon first
-                mView.updateIcon(ICON_LOCK, false);
-            }
             mView.updateIcon(ICON_LOCK, true);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
@@ -294,11 +287,6 @@
             mView.setContentDescription(null);
         }
 
-        if (!mShowAODFpIcon && mAodFp != null) {
-            mAodFp.setVisibility(View.INVISIBLE);
-            mAodFp.setContentDescription(null);
-        }
-
         if (!Objects.equals(prevContentDescription, mView.getContentDescription())
                 && mView.getContentDescription() != null) {
             mView.announceForAccessibility(mView.getContentDescription());
@@ -317,7 +305,7 @@
                         getResources().getString(R.string.accessibility_enter_hint));
         public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
             super.onInitializeAccessibilityNodeInfo(v, info);
-            if (isClickable()) {
+            if (isActionable()) {
                 if (mShowLockIcon) {
                     info.addAction(mAccessibilityAuthenticateHint);
                 } else if (mShowUnlockIcon) {
@@ -386,7 +374,7 @@
         pw.println();
         pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
         pw.println(" mShowLockIcon: " + mShowLockIcon);
-        pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
+        pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon);
         pw.println("  mIsDozing: " + mIsDozing);
         pw.println("  mIsBouncerShowing: " + mIsBouncerShowing);
         pw.println("  mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
@@ -415,17 +403,8 @@
                         - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
         float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
 
-        if (mAodFp != null) {
-            mAodFp.setTranslationX(offsetX);
-            mAodFp.setTranslationY(offsetY);
-            mAodFp.setProgress(progress);
-            mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
-        }
-
-        if (mShowAodLockIcon) {
-            mView.setTranslationX(offsetX);
-            mView.setTranslationY(offsetY);
-        }
+        mView.setTranslationX(offsetX);
+        mView.setTranslationY(offsetY);
     }
 
     private void updateIsUdfpsEnrolled() {
@@ -436,10 +415,6 @@
         mView.setUseBackground(mUdfpsSupported);
 
         mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
-        if (!wasUdfpsEnrolled && mUdfpsEnrolled && mAodFp == null) {
-            mLayoutInflater.inflate(R.layout.udfps_aod_lock_icon, mView);
-            mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
-        }
         if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
             updateVisibility();
         }
@@ -474,7 +449,7 @@
                 @Override
                 public void onKeyguardVisibilityChanged(boolean showing) {
                     // reset mIsBouncerShowing state in case it was preemptively set
-                    // onAffordanceClick
+                    // onLongPress
                     mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
                     updateVisibility();
                 }
@@ -568,104 +543,75 @@
         }
     };
 
-    private final GestureDetector mGestureDetector =
-            new GestureDetector(new SimpleOnGestureListener() {
-                public boolean onDown(MotionEvent e) {
-                    if (!isClickable()) {
-                        mDownDetected = false;
-                        return false;
-                    }
-
-                    // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
-                    // MotionEvent.ACTION_UP (see #onTouchEvent)
-                    if (mVibrator != null && !mDownDetected) {
-                        mVibrator.vibrate(
-                                Process.myUid(),
-                                getContext().getOpPackageName(),
-                                UdfpsController.EFFECT_CLICK,
-                                "lockIcon-onDown",
-                                TOUCH_VIBRATION_ATTRIBUTES);
-                    }
-
-                    mDownDetected = true;
-                    return true;
-                }
-
-                public void onLongPress(MotionEvent e) {
-                    if (!wasClickableOnDownEvent()) {
-                        return;
-                    }
-
-                    if (onAffordanceClick() && mVibrator != null) {
-                        // only vibrate if the click went through and wasn't intercepted by falsing
-                        mVibrator.vibrate(
-                                Process.myUid(),
-                                getContext().getOpPackageName(),
-                                UdfpsController.EFFECT_CLICK,
-                                "lockIcon-onLongPress",
-                                TOUCH_VIBRATION_ATTRIBUTES);
-                    }
-                }
-
-                public boolean onSingleTapUp(MotionEvent e) {
-                    if (!wasClickableOnDownEvent()) {
-                        return false;
-                    }
-                    onAffordanceClick();
-                    return true;
-                }
-
-                public boolean onFling(MotionEvent e1, MotionEvent e2,
-                        float velocityX, float velocityY) {
-                    if (!wasClickableOnDownEvent()) {
-                        return false;
-                    }
-                    onAffordanceClick();
-                    return true;
-                }
-
-                private boolean wasClickableOnDownEvent() {
-                    return mDownDetected;
-                }
-
-                /**
-                 * Whether we tried to launch the affordance.
-                 *
-                 * If falsing intercepts the click, returns false.
-                 */
-                private boolean onAffordanceClick() {
-                    if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
-                        return false;
-                    }
-
-                    // pre-emptively set to true to hide view
-                    mIsBouncerShowing = true;
-                    if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
-                        mAuthRippleController.showRipple(FINGERPRINT);
-                    }
-                    updateVisibility();
-                    if (mOnGestureDetectedRunnable != null) {
-                        mOnGestureDetectedRunnable.run();
-                    }
-                    mKeyguardViewController.showBouncer(/* scrim */ true);
-                    return true;
-                }
-            });
-
     /**
-     * Send touch events to this view and handles it if the touch is within this view and we are
-     * in a 'clickable' state
-     * @return whether to intercept the touch event
+     * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true.
+     * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon
+     * area for {@link #LONG_PRESS_TIMEOUT} ms.
+     *
+     * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}.
      */
     public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
-        if (onInterceptTouchEvent(event)) {
-            mOnGestureDetectedRunnable = onGestureDetectedRunnable;
-            mGestureDetector.onTouchEvent(event);
-            return true;
+        if (!onInterceptTouchEvent(event)) {
+            cancelTouches();
+            return false;
         }
 
-        mDownDetected = false;
-        return false;
+        mOnGestureDetectedRunnable = onGestureDetectedRunnable;
+        switch(event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_HOVER_ENTER:
+                if (mVibrator != null && !mDownDetected) {
+                    mVibrator.vibrate(
+                            Process.myUid(),
+                            getContext().getOpPackageName(),
+                            UdfpsController.EFFECT_CLICK,
+                            "lock-icon-down",
+                            TOUCH_VIBRATION_ATTRIBUTES);
+                }
+
+                // The pointer that causes ACTION_DOWN is always at index 0.
+                // We need to persist its ID to track it during ACTION_MOVE that could include
+                // data for many other pointers because of multi-touch support.
+                mActivePointerId = event.getPointerId(0);
+                if (mVelocityTracker == null) {
+                    // To simplify the lifecycle of the velocity tracker, make sure it's never null
+                    // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
+                    mVelocityTracker = VelocityTracker.obtain();
+                } else {
+                    // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
+                    // ACTION_DOWN, in that case we should just reuse the old instance.
+                    mVelocityTracker.clear();
+                }
+                mVelocityTracker.addMovement(event);
+
+                mDownDetected = true;
+                mLongPressCancelRunnable = mExecutor.executeDelayed(
+                        this::onLongPress, LONG_PRESS_TIMEOUT);
+                break;
+            case MotionEvent.ACTION_MOVE:
+            case MotionEvent.ACTION_HOVER_MOVE:
+                mVelocityTracker.addMovement(event);
+                // Compute pointer velocity in pixels per second.
+                mVelocityTracker.computeCurrentVelocity(1000);
+                float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
+                        mActivePointerId);
+                if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
+                        && UdfpsController.exceedsVelocityThreshold(velocity)) {
+                    Log.v(TAG, "lock icon long-press rescheduled due to "
+                            + "high pointer velocity=" + velocity);
+                    mLongPressCancelRunnable.run();
+                    mLongPressCancelRunnable = mExecutor.executeDelayed(
+                            this::onLongPress, LONG_PRESS_TIMEOUT);
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_HOVER_EXIT:
+                cancelTouches();
+                break;
+        }
+
+        return true;
     }
 
     /**
@@ -673,7 +619,7 @@
      * bounds.
      */
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (!inLockIconArea(event) || !isClickable()) {
+        if (!inLockIconArea(event) || !isActionable()) {
             return false;
         }
 
@@ -684,13 +630,58 @@
         return mDownDetected;
     }
 
-    private boolean inLockIconArea(MotionEvent event) {
-        return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
-                && (mView.getVisibility() == View.VISIBLE
-                || (mAodFp != null && mAodFp.getVisibility() == View.VISIBLE));
+    private void onLongPress() {
+        cancelTouches();
+        if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+            Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
+            return;
+        }
+
+        // pre-emptively set to true to hide view
+        mIsBouncerShowing = true;
+        if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+            mAuthRippleController.showRipple(FINGERPRINT);
+        }
+        updateVisibility();
+        if (mOnGestureDetectedRunnable != null) {
+            mOnGestureDetectedRunnable.run();
+        }
+
+        if (mVibrator != null) {
+            // play device entry haptic (same as biometric success haptic)
+            mVibrator.vibrate(
+                    Process.myUid(),
+                    getContext().getOpPackageName(),
+                    UdfpsController.EFFECT_CLICK,
+                    "lock-icon-device-entry",
+                    TOUCH_VIBRATION_ATTRIBUTES);
+        }
+
+        mKeyguardViewController.showBouncer(/* scrim */ true);
     }
 
-    private boolean isClickable() {
+
+    private void cancelTouches() {
+        mDownDetected = false;
+        if (mLongPressCancelRunnable != null) {
+            mLongPressCancelRunnable.run();
+        }
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+        if (mVibrator != null) {
+            mVibrator.cancel();
+        }
+    }
+
+
+    private boolean inLockIconArea(MotionEvent event) {
+        return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
+                && mView.getVisibility() == View.VISIBLE;
+    }
+
+    private boolean isActionable() {
         return mUdfpsSupported || mShowUnlockIcon;
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
index 5e874ae..3361015 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
@@ -161,6 +161,7 @@
             // No animation is requested, thus set base and target state to the same state.
             textInterpolator.progress = 1f
             textInterpolator.rebase()
+            invalidateCallback()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index ce493d0..08ed24c 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -102,10 +102,10 @@
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -366,7 +366,7 @@
     @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
     @Inject Lazy<NotificationSectionsManager> mNotificationSectionsManagerLazy;
-    @Inject Lazy<UnlockedScreenOffAnimationController> mUnlockedScreenOffAnimationControllerLazy;
+    @Inject Lazy<ScreenOffAnimationController> mScreenOffAnimationController;
     @Inject Lazy<AmbientState> mAmbientStateLazy;
     @Inject Lazy<GroupMembershipManager> mGroupMembershipManagerLazy;
     @Inject Lazy<GroupExpansionManager> mGroupExpansionManagerLazy;
@@ -586,8 +586,7 @@
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
         mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
         mProviders.put(NotificationSectionsManager.class, mNotificationSectionsManagerLazy::get);
-        mProviders.put(UnlockedScreenOffAnimationController.class,
-                mUnlockedScreenOffAnimationControllerLazy::get);
+        mProviders.put(ScreenOffAnimationController.class, mScreenOffAnimationController::get);
         mProviders.put(AmbientState.class, mAmbientStateLazy::get);
         mProviders.put(GroupMembershipManager.class, mGroupMembershipManagerLazy::get);
         mProviders.put(GroupExpansionManager.class, mGroupExpansionManagerLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index d1739aa..8ff90f7 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -126,6 +126,7 @@
             mRenderer = getRendererInstance();
             setFixedSizeAllowed(true);
             updateSurfaceSize();
+            setShowForAllUsers(true);
 
             mRenderer.setOnBitmapChanged(b -> {
                 mLocalColorsToAdd.addAll(mColorAreas);
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
index 47adffc..45077d2 100644
--- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
@@ -50,10 +50,13 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        // Verify intent is valid
         mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
         mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
-        if (mUri == null) {
-            Log.e(TAG, SliceProvider.EXTRA_BIND_URI + " wasn't provided");
+        if (mUri == null
+                || !SliceProvider.SLICE_TYPE.equals(getContentResolver().getType(mUri))
+                || !SliceManager.ACTION_REQUEST_SLICE_PERMISSION.equals(getIntent().getAction())) {
+            Log.e(TAG, "Intent is not valid");
             finish();
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 8c405ca..63962fa 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui;
 
+import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.Application;
 import android.app.Notification;
@@ -27,6 +28,7 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -111,6 +113,13 @@
                         ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
             }
 
+            // Enable binder tracing on system server for calls originating from SysUI
+            try {
+                ActivityManager.getService().enableBinderTracing();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to enable binder tracing", e);
+            }
+
             registerReceiver(new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 251c1e6..2767904 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -121,7 +121,7 @@
                     .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
                     .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
                     .setRecentTasks(mWMComponent.getRecentTasks())
-                    .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI()))
+                    .setCompatUI(Optional.of(mWMComponent.getCompatUI()))
                     .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop()));
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
@@ -141,7 +141,7 @@
                     .setStartingSurface(Optional.ofNullable(null))
                     .setTaskSurfaceHelper(Optional.ofNullable(null))
                     .setRecentTasks(Optional.ofNullable(null))
-                    .setSizeCompatUI(Optional.ofNullable(null))
+                    .setCompatUI(Optional.ofNullable(null))
                     .setDragAndDrop(Optional.ofNullable(null));
         }
         mSysUIComponent = builder.build();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
index f13730e..3f5c2c8 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/PhoneStateMonitor.java
@@ -171,8 +171,8 @@
         return mStatusBarStateController.isDozing();
     }
 
-    private boolean isLauncherShowing(ActivityManager.RunningTaskInfo runningTaskInfo) {
-        if (runningTaskInfo == null) {
+    private boolean isLauncherShowing(@Nullable ActivityManager.RunningTaskInfo runningTaskInfo) {
+        if (runningTaskInfo == null || runningTaskInfo.topActivity == null) {
             return false;
         } else {
             return runningTaskInfo.topActivity.equals(mDefaultHome);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 39088c3..f8e7697 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -135,6 +135,8 @@
         LayoutTransition transition = new LayoutTransition();
         transition.setDuration(200);
 
+        // Animates appearing/disappearing of the battery percentage text using fade-in/fade-out
+        // and disables all other animation types
         ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
         transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
         transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
@@ -143,6 +145,10 @@
         transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
         transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
 
+        transition.setAnimator(LayoutTransition.CHANGE_APPEARING, null);
+        transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null);
+        transition.setAnimator(LayoutTransition.CHANGING, null);
+
         setLayoutTransition(transition);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index f11dc93..1496f17 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -636,6 +636,9 @@
         mIndicatorView.setText(message);
         mIndicatorView.setTextColor(mTextColorError);
         mIndicatorView.setVisibility(View.VISIBLE);
+        // select to enable marquee unless a screen reader is enabled
+        mIndicatorView.setSelected(!mAccessibilityManager.isEnabled()
+                || !mAccessibilityManager.isTouchExplorationEnabled());
         mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError());
 
         Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 29e5574..6edf2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -31,6 +31,7 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.PointF;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -89,6 +90,7 @@
 
     private static final String TAG = "AuthController";
     private static final boolean DEBUG = true;
+    private static final int SENSOR_PRIVACY_DELAY = 500;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final CommandQueue mCommandQueue;
@@ -122,6 +124,7 @@
     @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
 
     @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+    private SensorPrivacyManager mSensorPrivacyManager;
 
     private class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -492,6 +495,7 @@
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
 
         context.registerReceiver(mBroadcastReceiver, filter);
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
     }
 
     private void updateFingerprintLocation() {
@@ -642,10 +646,16 @@
         final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
                 || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
 
+        boolean isCameraPrivacyEnabled = false;
+        if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE
+                && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+                mCurrentDialogArgs.argi1 /* userId */)) {
+            isCameraPrivacyEnabled = true;
+        }
         // TODO(b/141025588): Create separate methods for handling hard and soft errors.
         final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
-                || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
-
+                || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
+                || isCameraPrivacyEnabled);
         if (mCurrentDialog != null) {
             if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
                 if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
@@ -655,12 +665,23 @@
                         ? mContext.getString(R.string.biometric_not_recognized)
                         : getErrorString(modality, error, vendorCode);
                 if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
-                mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
+                // The camera privacy error can return before the prompt initializes its state,
+                // causing the prompt to appear to endlessly authenticate. Add a small delay
+                // to stop this.
+                if (isCameraPrivacyEnabled) {
+                    mHandler.postDelayed(() -> {
+                        mCurrentDialog.onAuthenticationFailed(modality,
+                                mContext.getString(R.string.face_sensor_privacy_enabled));
+                    }, SENSOR_PRIVACY_DELAY);
+                } else {
+                    mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
+                }
             } else {
                 final String errorMessage = getErrorString(modality, error, vendorCode);
                 if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
                 mCurrentDialog.onError(modality, errorMessage);
             }
+
         } else {
             Log.w(TAG, "onBiometricError callback but dialog is gone");
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
index fb4616a..73e3aec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java
@@ -23,6 +23,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.util.ViewController;
@@ -44,6 +45,7 @@
         extends ViewController<T> implements Dumpable {
     @NonNull final StatusBarStateController mStatusBarStateController;
     @NonNull final PanelExpansionStateManager mPanelExpansionStateManager;
+    @NonNull final SystemUIDialogManager mDialogManager;
     @NonNull final DumpManager mDumpManger;
 
     boolean mNotificationShadeVisible;
@@ -52,10 +54,12 @@
             T view,
             @NonNull StatusBarStateController statusBarStateController,
             @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull SystemUIDialogManager dialogManager,
             @NonNull DumpManager dumpManager) {
         super(view);
         mStatusBarStateController = statusBarStateController;
         mPanelExpansionStateManager = panelExpansionStateManager;
+        mDialogManager = dialogManager;
         mDumpManger = dumpManager;
     }
 
@@ -64,12 +68,14 @@
     @Override
     protected void onViewAttached() {
         mPanelExpansionStateManager.addExpansionListener(mPanelExpansionListener);
+        mDialogManager.registerListener(mDialogListener);
         mDumpManger.registerDumpable(getDumpTag(), this);
     }
 
     @Override
     protected void onViewDetached() {
         mPanelExpansionStateManager.removeExpansionListener(mPanelExpansionListener);
+        mDialogManager.unregisterListener(mDialogListener);
         mDumpManger.unregisterDumpable(getDumpTag());
     }
 
@@ -95,7 +101,8 @@
      * authentication.
      */
     boolean shouldPauseAuth() {
-        return mNotificationShadeVisible;
+        return mNotificationShadeVisible
+                || mDialogManager.shouldHideAffordance();
     }
 
     /**
@@ -189,4 +196,7 @@
             updatePauseAuth();
         }
     };
+
+    private final SystemUIDialogManager.Listener mDialogListener =
+            (shouldHide) -> updatePauseAuth();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
index 894b295..3732100 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 
 /**
@@ -30,8 +31,10 @@
             @NonNull UdfpsBpView view,
             @NonNull StatusBarStateController statusBarStateController,
             @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull DumpManager dumpManager) {
-        super(view, statusBarStateController, panelExpansionStateManager, dumpManager);
+        super(view, statusBarStateController, panelExpansionStateManager,
+                systemUIDialogManager, dumpManager);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3f077f5..1da9f21 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -47,7 +47,6 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.provider.Settings;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -71,6 +70,7 @@
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 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.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -103,6 +103,7 @@
 public class UdfpsController implements DozeReceiver {
     private static final String TAG = "UdfpsController";
     private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
+    private static final long DEFAULT_VIBRATION_DURATION = 1000; // milliseconds
 
     // Minimum required delay between consecutive touch logs in milliseconds.
     private static final long MIN_TOUCH_LOG_INTERVAL = 50;
@@ -118,6 +119,7 @@
     @NonNull private final KeyguardStateController mKeyguardStateController;
     @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
     @NonNull private final DumpManager mDumpManager;
+    @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Nullable private final Vibrator mVibrator;
     @NonNull private final FalsingManager mFalsingManager;
@@ -164,10 +166,6 @@
     private boolean mAttemptedToDismissKeyguard;
     private Set<Callback> mCallbacks = new HashSet<>();
 
-    // by default, use low tick
-    private int mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
-    private final VibrationEffect mTick;
-
     @VisibleForTesting
     public static final VibrationAttributes VIBRATION_ATTRIBUTES =
             new VibrationAttributes.Builder()
@@ -281,9 +279,6 @@
                     return;
                 }
                 mGoodCaptureReceived = true;
-                if (mVibrator != null) {
-                    mVibrator.cancel();
-                }
                 mView.stopIllumination();
                 if (mServerRequest != null) {
                     mServerRequest.onAcquiredGood();
@@ -326,12 +321,23 @@
         }
     }
 
-    private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
+    /**
+     * Calculate the pointer speed given a velocity tracker and the pointer id.
+     * This assumes that the velocity tracker has already been passed all relevant motion events.
+     */
+    public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) {
         final float vx = tracker.getXVelocity(pointerId);
         final float vy = tracker.getYVelocity(pointerId);
         return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0));
     }
 
+    /**
+     * Whether the velocity exceeds the acceptable UDFPS debouncing threshold.
+     */
+    public static boolean exceedsVelocityThreshold(float velocity) {
+        return velocity > 750f;
+    }
+
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -466,7 +472,7 @@
                         final float v = computePointerSpeed(mVelocityTracker, mActivePointerId);
                         final float minor = event.getTouchMinor(idx);
                         final float major = event.getTouchMajor(idx);
-                        final boolean exceedsVelocityThreshold = v > 750f;
+                        final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v);
                         final String touchInfo = String.format(
                                 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
                                 minor, major, v, exceedsVelocityThreshold);
@@ -547,7 +553,8 @@
             @Main Handler mainHandler,
             @NonNull ConfigurationController configurationController,
             @NonNull SystemClock systemClock,
-            @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+            @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            @NonNull SystemUIDialogManager dialogManager) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -562,6 +569,7 @@
         mKeyguardStateController = keyguardStateController;
         mKeyguardViewManager = statusBarKeyguardViewManager;
         mDumpManager = dumpManager;
+        mDialogManager = dialogManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mFalsingManager = falsingManager;
         mPowerManager = powerManager;
@@ -574,7 +582,6 @@
         mConfigurationController = configurationController;
         mSystemClock = systemClock;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mTick = lowTick();
 
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
@@ -609,36 +616,6 @@
         udfpsHapticsSimulator.setUdfpsController(this);
     }
 
-    private VibrationEffect lowTick() {
-        boolean useLowTickDefault = mContext.getResources()
-                .getBoolean(R.bool.config_udfpsUseLowTick);
-        if (Settings.Global.getFloat(
-                mContext.getContentResolver(),
-                "tick-low", useLowTickDefault ? 1 : 0) == 0) {
-            mPrimitiveTick = VibrationEffect.Composition.PRIMITIVE_TICK;
-        }
-        float tickIntensity = Settings.Global.getFloat(
-                mContext.getContentResolver(),
-                "tick-intensity",
-                mContext.getResources().getFloat(R.dimen.config_udfpsTickIntensity));
-        int tickDelay = Settings.Global.getInt(
-                mContext.getContentResolver(),
-                "tick-delay",
-                mContext.getResources().getInteger(R.integer.config_udfpsTickDelay));
-
-        VibrationEffect.Composition composition = VibrationEffect.startComposition();
-        composition.addPrimitive(mPrimitiveTick, tickIntensity, 0);
-        int primitives = 1000 / tickDelay;
-        float[] rampUp = new float[]{.48f, .58f, .69f, .83f};
-        for (int i = 0; i < rampUp.length; i++) {
-            composition.addPrimitive(mPrimitiveTick, tickIntensity * rampUp[i], tickDelay);
-        }
-        for (int i = rampUp.length; i < primitives; i++) {
-            composition.addPrimitive(mPrimitiveTick, tickIntensity, tickDelay);
-        }
-        return composition.compose();
-    }
-
     /**
      * Play haptic to signal udfps scanning started.
      */
@@ -648,8 +625,8 @@
             mVibrator.vibrate(
                     Process.myUid(),
                     mContext.getOpPackageName(),
-                    mTick,
-                    "udfps-onStart-tick",
+                    EFFECT_CLICK,
+                    "udfps-onStart-click",
                     VIBRATION_ATTRIBUTES);
         }
     }
@@ -841,6 +818,7 @@
                         mServerRequest.mEnrollHelper,
                         mStatusBarStateController,
                         mPanelExpansionStateManager,
+                        mDialogManager,
                         mDumpManager
                 );
             case BiometricOverlayConstants.REASON_AUTH_KEYGUARD:
@@ -859,6 +837,7 @@
                         mSystemClock,
                         mKeyguardStateController,
                         mUnlockedScreenOffAnimationController,
+                        mDialogManager,
                         this
                 );
             case BiometricOverlayConstants.REASON_AUTH_BP:
@@ -869,6 +848,7 @@
                         bpView,
                         mStatusBarStateController,
                         mPanelExpansionStateManager,
+                        mDialogManager,
                         mDumpManager
                 );
             case BiometricOverlayConstants.REASON_AUTH_OTHER:
@@ -880,6 +860,7 @@
                         authOtherView,
                         mStatusBarStateController,
                         mPanelExpansionStateManager,
+                        mDialogManager,
                         mDumpManager
                 );
             default:
@@ -1036,7 +1017,6 @@
             }
         }
         mOnFingerDown = false;
-        mVibrator.cancel();
         if (mView.isIlluminationRequested()) {
             mView.stopIllumination();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index 1f01fc5..ac38b13 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -20,9 +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.Color;
 import android.graphics.Paint;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -30,7 +28,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.TypedValue;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 
@@ -38,7 +35,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.R;
 
 /**
@@ -106,9 +102,8 @@
 
         mSensorOutlinePaint = new Paint(0 /* flags */);
         mSensorOutlinePaint.setAntiAlias(true);
-        mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
-        mSensorOutlinePaint.setStyle(Paint.Style.STROKE);
-        mSensorOutlinePaint.setStrokeWidth(2.f);
+        mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_moving_target_fill));
+        mSensorOutlinePaint.setStyle(Paint.Style.FILL);
 
         mBlueFill = new Paint(0 /* flags */);
         mBlueFill.setAntiAlias(true);
@@ -117,12 +112,12 @@
 
         mMovingTargetFpIcon = context.getResources()
                 .getDrawable(R.drawable.ic_kg_fingerprint, null);
-        mMovingTargetFpIcon.setTint(Color.WHITE);
+        mMovingTargetFpIcon.setTint(mContext.getColor(R.color.udfps_enroll_icon));
         mMovingTargetFpIcon.mutate();
 
         mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
 
-        mHintColorFaded = getHintColorFaded(context);
+        mHintColorFaded = context.getColor(R.color.udfps_moving_target_fill);
         mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
         mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
         mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
@@ -218,22 +213,6 @@
         };
     }
 
-    @ColorInt
-    private static int getHintColorFaded(@NonNull Context context) {
-        final TypedValue tv = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
-        final int alpha = (int) (tv.getFloat() * 255f);
-
-        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
-        final TypedArray ta = context.obtainStyledAttributes(attrs);
-        try {
-            @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
-            return ColorUtils.setAlphaComponent(color, alpha);
-        } finally {
-            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 79c7e66..631a461 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -18,12 +18,10 @@
 
 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;
 import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
 import android.view.animation.Interpolator;
 import android.view.animation.OvershootInterpolator;
 
@@ -80,24 +78,11 @@
 
         mBackgroundPaint = new Paint();
         mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
-        mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
+        mBackgroundPaint.setColor(context.getColor(R.color.udfps_moving_target_fill));
         mBackgroundPaint.setAntiAlias(true);
         mBackgroundPaint.setStyle(Paint.Style.STROKE);
         mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
 
-        // Set background paint color and alpha.
-        final int[] attrs = new int[] {android.R.attr.colorControlNormal};
-        final TypedArray typedArray = context.obtainStyledAttributes(attrs);
-        try {
-            @ColorInt final int tintColor = typedArray.getColor(0, mBackgroundPaint.getColor());
-            mBackgroundPaint.setColor(tintColor);
-        } finally {
-            typedArray.recycle();
-        }
-        TypedValue alpha = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
-        mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
-
         // Progress fill should *not* use the extracted system color.
         mFillPaint = new Paint();
         mFillPaint.setStrokeWidth(mStrokeWidthPx);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 292a904..ac9e92e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -22,6 +22,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 
 /**
@@ -54,8 +55,10 @@
             @NonNull UdfpsEnrollHelper enrollHelper,
             @NonNull StatusBarStateController statusBarStateController,
             @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull DumpManager dumpManager) {
-        super(view, statusBarStateController, panelExpansionStateManager, dumpManager);
+        super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+                dumpManager);
         mEnrollProgressBarRadius = getContext().getResources()
                 .getInteger(R.integer.config_udfpsEnrollProgressBar);
         mEnrollHelper = enrollHelper;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
index 6198733..97dca0f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmOtherViewController.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 
 /**
@@ -33,8 +34,10 @@
             @NonNull UdfpsFpmOtherView view,
             @NonNull StatusBarStateController statusBarStateController,
             @NonNull PanelExpansionStateManager panelExpansionStateManager,
+            @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull DumpManager dumpManager) {
-        super(view, statusBarStateController, panelExpansionStateManager, dumpManager);
+        super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+                dumpManager);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 8f4d6f6..3e8a568 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 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.phone.panelstate.PanelExpansionListener;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
@@ -86,8 +87,10 @@
             @NonNull SystemClock systemClock,
             @NonNull KeyguardStateController keyguardStateController,
             @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            @NonNull SystemUIDialogManager systemUIDialogManager,
             @NonNull UdfpsController udfpsController) {
-        super(view, statusBarStateController, panelExpansionStateManager, dumpManager);
+        super(view, statusBarStateController, panelExpansionStateManager, systemUIDialogManager,
+                dumpManager);
         mKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockScreenShadeTransitionController = transitionController;
@@ -217,6 +220,10 @@
             return false;
         }
 
+        if (mDialogManager.shouldHideAffordance()) {
+            return true;
+        }
+
         if (mLaunchTransitionFadingAway) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
index 71edbc0..d17eadd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/DiagonalClassifier.java
@@ -19,6 +19,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_HORIZONTAL_ANGLE_RANGE;
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_DIAGONAL_VERTICAL_ANGLE_RANGE;
 import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.RIGHT_AFFORDANCE;
 
 import android.provider.DeviceConfig;
@@ -71,7 +72,9 @@
             return Result.passed(0);
         }
 
-        if (interactionType == LEFT_AFFORDANCE || interactionType == RIGHT_AFFORDANCE) {
+        if (interactionType == LEFT_AFFORDANCE
+                || interactionType == RIGHT_AFFORDANCE
+                || interactionType == LOCK_ICON) {
             return Result.passed(0);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java
index 73e5afe..b428380 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java
@@ -33,7 +33,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.ViewController;
 
@@ -175,7 +175,7 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             KeyguardStateController keyguardStateController,
             DozeParameters dozeParameters,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             StatusBarStateController statusBarStateController, CommunalHostView view) {
         super(view);
         mCommunalStateController = communalStateController;
@@ -184,7 +184,7 @@
         mKeyguardStateController = keyguardStateController;
         mStatusBarStateController = statusBarStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, communalStateController,
-                keyguardStateController, dozeParameters, unlockedScreenOffAnimationController,
+                keyguardStateController, dozeParameters, screenOffAnimationController,
                 /* animateYPos= */ false, /* visibleOnCommunal= */ true);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java
index 53586f5..42ecd5c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSource.java
@@ -35,7 +35,23 @@
      * {@link Connector} defines an interface for {@link CommunalSource} instances to be generated.
      */
     interface Connector {
-        ListenableFuture<Optional<CommunalSource>> connect();
+        Connection connect(Connection.Callback callback);
+    }
+
+    /**
+     * {@link Connection} defines an interface for an entity which holds the necessary components
+     * for establishing and maintaining a connection to the communal source.
+     */
+    interface Connection {
+        /**
+         * {@link Callback} defines an interface for clients to be notified when a source is ready
+         */
+        interface Callback {
+            void onSourceEstablished(Optional<CommunalSource> source);
+            void onDisconnected();
+        }
+
+        void disconnect();
     }
 
     /**
@@ -86,29 +102,4 @@
      * value will be {@code null} in case of a failure.
      */
     ListenableFuture<CommunalViewResult> requestCommunalView(Context context);
-
-    /**
-     * Adds a {@link Callback} to receive future status updates regarding this
-     * {@link CommunalSource}.
-     *
-     * @param callback The {@link Callback} to be added.
-     */
-    void addCallback(Callback callback);
-
-    /**
-     * Removes a {@link Callback} from receiving future updates.
-     *
-     * @param callback The {@link Callback} to be removed.
-     */
-    void removeCallback(Callback callback);
-
-    /**
-     * An interface for receiving updates on the state of the {@link CommunalSource}.
-     */
-    interface Callback {
-        /**
-         * Invoked when the {@link CommunalSource} is no longer available for use.
-         */
-        void onDisconnected();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
index d3018e3..58cf35f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java
@@ -16,19 +16,24 @@
 
 package com.android.systemui.communal;
 
+import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
+
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.condition.Monitor;
 
 import com.google.android.collect.Lists;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * A Monitor for reporting a {@link CommunalSource} presence.
@@ -40,7 +45,8 @@
 
     // A list of {@link Callback} that have registered to receive updates.
     private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
-    private final CommunalConditionsMonitor mConditionsMonitor;
+    private final Monitor mConditionsMonitor;
+    private final Executor mExecutor;
 
     private CommunalSource mCurrentSource;
 
@@ -50,15 +56,7 @@
     // Whether the class is currently listening for condition changes.
     private boolean mListeningForConditions = false;
 
-    private CommunalSource.Callback mSourceCallback = new CommunalSource.Callback() {
-        @Override
-        public void onDisconnected() {
-            // Clear source reference.
-            setSource(null /* source */);
-        }
-    };
-
-    private final CommunalConditionsMonitor.Callback mConditionsCallback =
+    private final Monitor.Callback mConditionsCallback =
             allConditionsMet -> {
                 if (mAllCommunalConditionsMet != allConditionsMet) {
                     if (DEBUG) Log.d(TAG, "communal conditions changed: " + allConditionsMet);
@@ -70,7 +68,9 @@
 
     @VisibleForTesting
     @Inject
-    public CommunalSourceMonitor(CommunalConditionsMonitor communalConditionsMonitor) {
+    public CommunalSourceMonitor(@Main Executor executor,
+            @Named(COMMUNAL_CONDITIONS) Monitor communalConditionsMonitor) {
+        mExecutor = executor;
         mConditionsMonitor = communalConditionsMonitor;
     }
 
@@ -81,35 +81,28 @@
      * @param source The new {@link CommunalSource}.
      */
     public void setSource(CommunalSource source) {
-        if (mCurrentSource != null) {
-            mCurrentSource.removeCallback(mSourceCallback);
-        }
-
         mCurrentSource = source;
 
         if (mAllCommunalConditionsMet) {
             executeOnSourceAvailableCallbacks();
         }
-
-        // Add callback to be informed when the source disconnects.
-        if (mCurrentSource != null) {
-            mCurrentSource.addCallback(mSourceCallback);
-        }
     }
 
     private void executeOnSourceAvailableCallbacks() {
-        // If the new source is valid, inform registered Callbacks of its presence.
-        Iterator<WeakReference<Callback>> itr = mCallbacks.iterator();
-        while (itr.hasNext()) {
-            Callback cb = itr.next().get();
-            if (cb == null) {
-                itr.remove();
-            } else {
-                cb.onSourceAvailable(
-                        (mAllCommunalConditionsMet && mCurrentSource != null) ? new WeakReference<>(
-                                mCurrentSource) : null);
+        mExecutor.execute(() -> {
+            // If the new source is valid, inform registered Callbacks of its presence.
+            Iterator<WeakReference<Callback>> itr = mCallbacks.iterator();
+            while (itr.hasNext()) {
+                Callback cb = itr.next().get();
+                if (cb == null) {
+                    itr.remove();
+                } else {
+                    cb.onSourceAvailable(
+                            (mAllCommunalConditionsMet && mCurrentSource != null)
+                                    ? new WeakReference<>(mCurrentSource) : null);
+                }
             }
-        }
+        });
     }
 
     /**
@@ -118,17 +111,19 @@
      * @param callback The {@link Callback} to add.
      */
     public void addCallback(Callback callback) {
-        mCallbacks.add(new WeakReference<>(callback));
+        mExecutor.execute(() -> {
+            mCallbacks.add(new WeakReference<>(callback));
 
-        // Inform the callback of any already present CommunalSource.
-        if (mAllCommunalConditionsMet && mCurrentSource != null) {
-            callback.onSourceAvailable(new WeakReference<>(mCurrentSource));
-        }
+            // Inform the callback of any already present CommunalSource.
+            if (mAllCommunalConditionsMet && mCurrentSource != null) {
+                callback.onSourceAvailable(new WeakReference<>(mCurrentSource));
+            }
 
-        if (!mListeningForConditions) {
-            mConditionsMonitor.addCallback(mConditionsCallback);
-            mListeningForConditions = true;
-        }
+            if (!mListeningForConditions) {
+                mConditionsMonitor.addCallback(mConditionsCallback);
+                mListeningForConditions = true;
+            }
+        });
     }
 
     /**
@@ -137,12 +132,14 @@
      * @param callback The {@link Callback} to add.
      */
     public void removeCallback(Callback callback) {
-        mCallbacks.removeIf(el -> el.get() == callback);
+        mExecutor.execute(() -> {
+            mCallbacks.removeIf(el -> el.get() == callback);
 
-        if (mCallbacks.isEmpty() && mListeningForConditions) {
-            mConditionsMonitor.removeCallback(mConditionsCallback);
-            mListeningForConditions = false;
-        }
+            if (mCallbacks.isEmpty() && mListeningForConditions) {
+                mConditionsMonitor.removeCallback(mConditionsCallback);
+                mListeningForConditions = false;
+            }
+        });
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
index 1044239..f965431 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourcePrimer.java
@@ -27,8 +27,6 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.time.SystemClock;
 
-import com.google.common.util.concurrent.ListenableFuture;
-
 import java.util.Optional;
 
 import javax.inject.Inject;
@@ -53,10 +51,11 @@
 
     private int mReconnectAttempts = 0;
     private Runnable mCurrentReconnectCancelable;
-    private ListenableFuture<Optional<CommunalSource>> mGetSourceFuture;
 
-    private final Optional<CommunalSource.Connector> mConnector;
     private final Optional<CommunalSource.Observer> mObserver;
+    private final Optional<CommunalSource.Connector> mConnector;
+
+    private CommunalSource.Connection mCurrentConnection;
 
     private final Runnable mConnectRunnable = new Runnable() {
         @Override
@@ -66,6 +65,10 @@
         }
     };
 
+    private final CommunalSource.Observer.Callback mObserverCallback = () -> {
+        initiateConnectionAttempt();
+    };
+
     @Inject
     public CommunalSourcePrimer(Context context, @Main Resources resources,
             SystemClock clock,
@@ -132,44 +135,46 @@
     @Override
     protected void onBootCompleted() {
         if (mObserver.isPresent()) {
-            mObserver.get().addCallback(() -> initiateConnectionAttempt());
+            mObserver.get().addCallback(mObserverCallback);
         }
         initiateConnectionAttempt();
     }
 
     private void connect() {
         if (DEBUG) {
-            Log.d(TAG, "attempting to communal to communal source");
+            Log.d(TAG, "attempting to connect to communal source");
         }
 
-        if (mGetSourceFuture != null) {
+        if (mCurrentConnection != null) {
             if (DEBUG) {
                 Log.d(TAG, "canceling in-flight connection");
             }
-            mGetSourceFuture.cancel(true);
+            mCurrentConnection.disconnect();
         }
 
-        mGetSourceFuture = mConnector.get().connect();
-        mGetSourceFuture.addListener(() -> {
-            try {
-                final long startTime = mSystemClock.currentTimeMillis();
-                Optional<CommunalSource> result = mGetSourceFuture.get();
-                if (result.isPresent()) {
-                    final CommunalSource source = result.get();
-                    source.addCallback(() -> {
-                        if (mSystemClock.currentTimeMillis() - startTime > mMinConnectionDuration) {
-                            initiateConnectionAttempt();
-                        } else {
-                            scheduleConnectionAttempt();
-                        }
-                    });
+        mCurrentConnection = mConnector.get().connect(new CommunalSource.Connection.Callback() {
+            private long mStartTime;
+
+            @Override
+            public void onSourceEstablished(Optional<CommunalSource> optionalSource) {
+                mStartTime = mSystemClock.currentTimeMillis();
+
+                if (optionalSource.isPresent()) {
+                    final CommunalSource source = optionalSource.get();
                     mMonitor.setSource(source);
                 } else {
                     scheduleConnectionAttempt();
                 }
-            } catch (Exception e) {
-                e.printStackTrace();
             }
-        }, mMainExecutor);
+
+            @Override
+            public void onDisconnected() {
+                if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
+                    initiateConnectionAttempt();
+                } else {
+                    scheduleConnectionAttempt();
+                }
+            }
+        });
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java
new file mode 100644
index 0000000..3d25d12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java
@@ -0,0 +1,101 @@
+/*
+ * 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.communal;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.PatternMatcher;
+import android.util.Log;
+
+import com.google.android.collect.Lists;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * {@link PackageObserver} allows for monitoring the system for changes relating to a particular
+ * package. This can be used by {@link CommunalSource} clients to detect when a related package
+ * has changed and reloading is necessary.
+ */
+public class PackageObserver implements CommunalSource.Observer {
+    private static final String TAG = "PackageObserver";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Log.d(TAG, "package added receiver - onReceive");
+            }
+
+            final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
+            while (iter.hasNext()) {
+                final Callback callback = iter.next().get();
+                if (callback != null) {
+                    callback.onSourceChanged();
+                } else {
+                    iter.remove();
+                }
+            }
+        }
+    };
+
+    private final String mPackageName;
+    private final Context mContext;
+
+    public PackageObserver(Context context, String packageName) {
+        mContext = context;
+        mPackageName = packageName;
+    }
+
+    @Override
+    public void addCallback(Callback callback) {
+        if (DEBUG) {
+            Log.d(TAG, "addCallback:" + callback);
+        }
+        mCallbacks.add(new WeakReference<>(callback));
+
+        // Only register for listening to package additions on first callback.
+        if (mCallbacks.size() > 1) {
+            return;
+        }
+
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
+        // Note that we directly register the receiver here as data schemes are not supported by
+        // BroadcastDispatcher.
+        mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
+    }
+
+    @Override
+    public void removeCallback(Callback callback) {
+        if (DEBUG) {
+            Log.d(TAG, "removeCallback:" + callback);
+        }
+        final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);
+
+        if (removed && mCallbacks.isEmpty()) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java
index 1616b18..25519d0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java
@@ -23,6 +23,7 @@
 
 import androidx.annotation.MainThread;
 
+import com.android.systemui.util.condition.Condition;
 import com.android.systemui.util.settings.SecureSettings;
 
 import javax.inject.Inject;
@@ -30,7 +31,7 @@
 /**
  * Monitors the communal setting, and informs any listeners with updates.
  */
-public class CommunalSettingCondition extends CommunalCondition {
+public class CommunalSettingCondition extends Condition {
     private final SecureSettings mSecureSettings;
     private final ContentObserver mCommunalSettingContentObserver;
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
index e4692db..2d59e13 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java
@@ -31,6 +31,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.util.condition.Condition;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.Arrays;
@@ -42,7 +43,7 @@
  * Monitors Wi-Fi connections and triggers callback, if any, when the device is connected to and
  * disconnected from a trusted network.
  */
-public class CommunalTrustedNetworkCondition extends CommunalCondition {
+public class CommunalTrustedNetworkCondition extends Condition {
     private final String mTag = getClass().getSimpleName();
     private final ConnectivityManager mConnectivityManager;
     private final ContentObserver mTrustedNetworksObserver;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
index 9b72655..e1f1ac4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java
@@ -16,26 +16,42 @@
 
 package com.android.systemui.communal.dagger;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Resources;
+import android.text.TextUtils;
 import android.view.View;
 import android.widget.FrameLayout;
 
-import com.android.systemui.communal.conditions.CommunalCondition;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+import com.android.systemui.communal.CommunalSource;
+import com.android.systemui.communal.PackageObserver;
 import com.android.systemui.communal.conditions.CommunalSettingCondition;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.idle.AmbientLightModeMonitor;
 import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
 import com.android.systemui.idle.dagger.IdleViewComponent;
+import com.android.systemui.util.condition.Condition;
+import com.android.systemui.util.condition.Monitor;
+import com.android.systemui.util.condition.dagger.MonitorComponent;
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Named;
+import javax.inject.Provider;
 
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 import dagger.multibindings.ElementsIntoSet;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.StringKey;
 
 /**
  * Dagger Module providing Communal-related functionality.
@@ -56,6 +72,20 @@
         return view;
     }
 
+    /** */
+    @Provides
+    static Optional<CommunalSource.Observer> provideCommunalSourcePackageObserver(
+            Context context, @Main Resources resources) {
+        final String componentName = resources.getString(R.string.config_communalSourceComponent);
+
+        if (TextUtils.isEmpty(componentName)) {
+            return Optional.empty();
+        }
+
+        return Optional.of(new PackageObserver(context,
+                ComponentName.unflattenFromString(componentName).getPackageName()));
+    }
+
     /**
      * Provides LightSensorEventsDebounceAlgorithm as an instance to DebounceAlgorithm interface.
      * @param algorithm the instance of algorithm that is bound to the interface.
@@ -71,8 +101,50 @@
     @Provides
     @ElementsIntoSet
     @Named(COMMUNAL_CONDITIONS)
-    static Set<CommunalCondition> provideCommunalConditions(
+    static Set<Condition> provideCommunalConditions(
             CommunalSettingCondition communalSettingCondition) {
         return new HashSet<>(Collections.singletonList(communalSettingCondition));
     }
+
+    /**
+     * TODO(b/205638389): Remove when there is a base implementation of
+     * {@link CommunalSource.Connector}. Currently a place holder to allow a map to be present.
+     */
+    @Provides
+    @IntoMap
+    @Nullable
+    @StringKey("empty")
+    static CommunalSource.Connector provideEmptyCommunalSourceConnector() {
+        return null;
+    }
+
+    /** */
+    @Provides
+    static Optional<CommunalSource.Connector> provideCommunalSourceConnector(
+            @Main Resources resources,
+            Map<Class<?>, Provider<CommunalSource.Connector>> connectorCreators) {
+        final String className = resources.getString(R.string.config_communalSourceConnector);
+
+        if (TextUtils.isEmpty(className)) {
+            return Optional.empty();
+        }
+
+        try {
+            Class<?> clazz = Class.forName(className);
+            Provider<CommunalSource.Connector> provider = connectorCreators.get(clazz);
+            return provider != null ? Optional.of(provider.get()) : Optional.empty();
+        } catch (ClassNotFoundException e) {
+            return Optional.empty();
+        }
+    }
+
+    /** */
+    @Provides
+    @Named(COMMUNAL_CONDITIONS)
+    static Monitor provideCommunalSourceMonitor(
+            @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions,
+            MonitorComponent.Factory factory) {
+        final MonitorComponent component = factory.create(communalConditions, new HashSet<>());
+        return component.getMonitor();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 38e4d78..af06979 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -24,14 +24,17 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.media.taptotransfer.MediaTttChipController;
+import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.people.PeopleProvider;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
+import com.android.systemui.unfold.UnfoldLatencyTracker;
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
 import com.android.wm.shell.draganddrop.DragAndDrop;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
@@ -39,7 +42,6 @@
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
@@ -111,7 +113,7 @@
         Builder setRecentTasks(Optional<RecentTasks> r);
 
         @BindsInstance
-        Builder setSizeCompatUI(Optional<SizeCompatUI> s);
+        Builder setCompatUI(Optional<CompatUI> s);
 
         @BindsInstance
         Builder setDragAndDrop(Optional<DragAndDrop> d);
@@ -133,6 +135,8 @@
         getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
         // No init method needed, just needs to be gotten so that it's created.
         getMediaTttChipController();
+        getMediaTttCommandLineHelper();
+        getUnfoldLatencyTracker().init();
     }
 
     /**
@@ -154,6 +158,12 @@
     ContextComponentHelper getContextComponentHelper();
 
     /**
+     * Creates a UnfoldLatencyTracker.
+     */
+    @SysUISingleton
+    UnfoldLatencyTracker getUnfoldLatencyTracker();
+
+    /**
      * Main dependency providing module.
      */
     @SysUISingleton
@@ -182,6 +192,9 @@
     /** */
     Optional<MediaTttChipController> getMediaTttChipController();
 
+    /** */
+    Optional<MediaTttCommandLineHelper> getMediaTttCommandLineHelper();
+
     /**
      * Member injection into the supplied argument.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 90a3ad2..b815d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -25,6 +25,7 @@
 import com.android.wm.shell.TaskViewFactory;
 import com.android.wm.shell.apppairs.AppPairs;
 import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.dagger.TvWMShellModule;
 import com.android.wm.shell.dagger.WMShellModule;
 import com.android.wm.shell.dagger.WMSingleton;
@@ -35,7 +36,6 @@
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.startingsurface.StartingSurface;
 import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
@@ -119,7 +119,7 @@
     Optional<RecentTasks> getRecentTasks();
 
     @WMSingleton
-    SizeCompatUI getSizeCompatUI();
+    CompatUI getCompatUI();
 
     @WMSingleton
     DragAndDrop getDragAndDrop();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index d27c39a..b8b4092 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -41,7 +41,6 @@
 import com.android.systemui.doze.dagger.WrappedService;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
@@ -99,8 +98,6 @@
      */
     private int mDebugBrightnessBucket = -1;
 
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
     @Inject
     public DozeScreenBrightness(
             Context context,
@@ -112,8 +109,7 @@
             WakefulnessLifecycle wakefulnessLifecycle,
             DozeParameters dozeParameters,
             DevicePostureController devicePostureController,
-            DozeLog dozeLog,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+            DozeLog dozeLog) {
         mContext = context;
         mDozeService = service;
         mSensorManager = sensorManager;
@@ -125,7 +121,6 @@
         mDozeHost = host;
         mHandler = handler;
         mDozeLog = dozeLog;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
 
         mScreenBrightnessMinimumDimAmountFloat = context.getResources().getFloat(
                 R.dimen.config_screenBrightnessMinimumDimAmountFloat);
@@ -267,7 +262,7 @@
      */
     private int clampToDimBrightnessForScreenOff(int brightness) {
         final boolean screenTurningOff =
-                mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                mDozeParameters.shouldClampToDimBrightness()
                         || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_GOING_TO_SLEEP;
         if (screenTurningOff
                 && mWakefulnessLifecycle.getLastSleepReason() == GO_TO_SLEEP_REASON_TIMEOUT) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 374bed3..b2fe3bb 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -37,21 +37,24 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.WakeLock;
 
 import java.util.Calendar;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * The policy controlling doze.
  */
 @DozeScope
 public class DozeUi implements DozeMachine.Part, TunerService.Tunable,
-        ConfigurationController.ConfigurationListener {
+        ConfigurationController.ConfigurationListener, FoldAodAnimationStatus,
+        StatusBarStateController.StateListener {
     // if enabled, calls dozeTimeTick() whenever the time changes:
     private static final boolean BURN_IN_TESTING_ENABLED = false;
     private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
@@ -59,12 +62,13 @@
     private final DozeHost mHost;
     private final Handler mHandler;
     private final WakeLock mWakeLock;
+    private final FoldAodAnimationController mFoldAodAnimationController;
     private DozeMachine mMachine;
     private final AlarmTimeout mTimeTicker;
     private final boolean mCanAnimateTransition;
     private final DozeParameters mDozeParameters;
     private final DozeLog mDozeLog;
-    private final Lazy<StatusBarStateController> mStatusBarStateController;
+    private final StatusBarStateController mStatusBarStateController;
     private final TunerService mTunerService;
     private final ConfigurationController mConfigurationController;
 
@@ -79,8 +83,7 @@
 
                 @Override
                 public void onTimeChanged() {
-                    if (BURN_IN_TESTING_ENABLED && mStatusBarStateController != null
-                            && mStatusBarStateController.get().isDozing()) {
+                    if (BURN_IN_TESTING_ENABLED && mStatusBarStateController.isDozing()) {
                         // update whenever the time changes for manual burn in testing
                         mHost.dozeTimeTick();
 
@@ -102,7 +105,8 @@
             WakeLock wakeLock, DozeHost host, @Main Handler handler,
             DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
             DozeLog dozeLog, TunerService tunerService,
-            Lazy<StatusBarStateController> statusBarStateController,
+            StatusBarStateController statusBarStateController,
+            Optional<SysUIUnfoldComponent> sysUiUnfoldComponent,
             ConfigurationController configurationController) {
         mContext = context;
         mWakeLock = wakeLock;
@@ -115,17 +119,29 @@
         mDozeLog = dozeLog;
         mTunerService = tunerService;
         mStatusBarStateController = statusBarStateController;
+        mStatusBarStateController.addCallback(this);
 
         mTunerService.addTunable(this, Settings.Secure.DOZE_ALWAYS_ON);
 
         mConfigurationController = configurationController;
         mConfigurationController.addCallback(this);
+
+        mFoldAodAnimationController = sysUiUnfoldComponent
+                .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
+
+        if (mFoldAodAnimationController != null) {
+            mFoldAodAnimationController.addCallback(this);
+        }
     }
 
     @Override
     public void destroy() {
         mTunerService.removeTunable(this);
         mConfigurationController.removeCallback(this);
+
+        if (mFoldAodAnimationController != null) {
+            mFoldAodAnimationController.removeCallback(this);
+        }
     }
 
     @Override
@@ -144,7 +160,8 @@
                     && (mKeyguardShowing || mDozeParameters.shouldControlUnlockedScreenOff())
                     && !mHost.isPowerSaveActive();
             mDozeParameters.setControlScreenOffAnimation(controlScreenOff);
-            mHost.setAnimateScreenOff(controlScreenOff);
+            mHost.setAnimateScreenOff(controlScreenOff
+                    && mDozeParameters.shouldAnimateDozingChange());
         }
     }
 
@@ -293,4 +310,17 @@
     public void onConfigChanged(Configuration newConfig) {
         updateAnimateScreenOff();
     }
+
+    /**
+     * Called when StatusBar state changed, could affect unlocked screen off animation state
+     */
+    @Override
+    public void onStatePostChange() {
+        updateAnimateScreenOff();
+    }
+
+    @Override
+    public void onFoldToAodAnimationChanged() {
+        updateAnimateScreenOff();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java
new file mode 100644
index 0000000..bc1f772
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerView.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+/**
+ * {@link DreamOverlayContainerView} contains a dream overlay and its status bar.
+ */
+public class DreamOverlayContainerView extends ConstraintLayout {
+    public DreamOverlayContainerView(Context context) {
+        this(context, null);
+    }
+
+    public DreamOverlayContainerView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DreamOverlayContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DreamOverlayContainerView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 8f0ea2f..393f039 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -29,11 +29,11 @@
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
-import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.PhoneWindow;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 
 import java.util.concurrent.Executor;
 
@@ -54,57 +54,75 @@
     private final Executor mExecutor;
     // The state controller informs the service of updates to the overlays present.
     private final DreamOverlayStateController mStateController;
+    // The component used to resolve dream overlay dependencies.
+    private final DreamOverlayComponent mDreamOverlayComponent;
 
-    // The window is populated once the dream informs the service it has begun dreaming.
-    private Window mWindow;
-    private ConstraintLayout mLayout;
+    // The dream overlay's content view, which is located below the status bar (in z-order) and is
+    // the space into which widgets are placed.
+    private ViewGroup mDreamOverlayContentView;
 
     private final DreamOverlayStateController.Callback mOverlayStateCallback =
             new DreamOverlayStateController.Callback() {
-        @Override
-        public void onOverlayChanged() {
-            mExecutor.execute(() -> reloadOverlaysLocked());
-        }
-    };
+                @Override
+                public void onOverlayChanged() {
+                    mExecutor.execute(() -> reloadOverlaysLocked());
+                }
+            };
 
     // The service listens to view changes in order to declare that input occurring in areas outside
     // the overlay should be passed through to the dream underneath.
-    private View.OnAttachStateChangeListener mRootViewAttachListener =
+    private final View.OnAttachStateChangeListener mRootViewAttachListener =
             new View.OnAttachStateChangeListener() {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            v.getViewTreeObserver()
-                    .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
-        }
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    v.getViewTreeObserver()
+                            .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+                }
 
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            v.getViewTreeObserver()
-                    .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
-        }
-    };
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                    v.getViewTreeObserver()
+                            .removeOnComputeInternalInsetsListener(
+                                    mOnComputeInternalInsetsListener);
+                }
+            };
 
     // A hook into the internal inset calculation where we declare the overlays as the only
     // touchable regions.
-    private ViewTreeObserver.OnComputeInternalInsetsListener mOnComputeInternalInsetsListener  =
+    private final ViewTreeObserver.OnComputeInternalInsetsListener
+            mOnComputeInternalInsetsListener =
             new ViewTreeObserver.OnComputeInternalInsetsListener() {
-        @Override
-        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
-            if (mLayout != null) {
-                inoutInfo.setTouchableInsets(
-                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-                final Region region = new Region();
-                for (int i = 0; i < mLayout.getChildCount(); i++) {
-                    View child = mLayout.getChildAt(i);
-                    final Rect rect = new Rect();
-                    child.getGlobalVisibleRect(rect);
-                    region.op(rect, Region.Op.UNION);
-                }
+                @Override
+                public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+                    if (mDreamOverlayContentView != null) {
+                        inoutInfo.setTouchableInsets(
+                                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                        final Region region = new Region();
+                        for (int i = 0; i < mDreamOverlayContentView.getChildCount(); i++) {
+                            View child = mDreamOverlayContentView.getChildAt(i);
+                            final Rect rect = new Rect();
+                            child.getGlobalVisibleRect(rect);
+                            region.op(rect, Region.Op.UNION);
+                        }
 
-                inoutInfo.touchableRegion.set(region);
-            }
-        }
-    };
+                        inoutInfo.touchableRegion.set(region);
+                    }
+                }
+            };
+
+    @Inject
+    public DreamOverlayService(
+            Context context,
+            @Main Executor executor,
+            DreamOverlayStateController overlayStateController,
+            DreamOverlayComponent.Factory dreamOverlayComponentFactory) {
+        mContext = context;
+        mExecutor = executor;
+        mStateController = overlayStateController;
+        mDreamOverlayComponent = dreamOverlayComponentFactory.create();
+
+        mStateController.addCallback(mOverlayStateCallback);
+    }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
@@ -112,10 +130,10 @@
     }
 
     private void reloadOverlaysLocked() {
-        if (mLayout == null) {
+        if (mDreamOverlayContentView == null) {
             return;
         }
-        mLayout.removeAllViews();
+        mDreamOverlayContentView.removeAllViews();
         for (OverlayProvider overlayProvider : mStateController.getOverlays()) {
             addOverlay(overlayProvider);
         }
@@ -129,31 +147,32 @@
      *                     into the dream window.
      */
     private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
-        mWindow = new PhoneWindow(mContext);
-        mWindow.setAttributes(layoutParams);
-        mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
+        final PhoneWindow window = new PhoneWindow(mContext);
+        window.setAttributes(layoutParams);
+        window.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
 
-        mWindow.setBackgroundDrawable(new ColorDrawable(0));
+        window.setBackgroundDrawable(new ColorDrawable(0));
 
-        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
-        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
-        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        window.requestFeature(Window.FEATURE_NO_TITLE);
         // Hide all insets when the dream is showing
-        mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
-        mWindow.setDecorFitsSystemWindows(false);
+        window.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
+        window.setDecorFitsSystemWindows(false);
 
         if (DEBUG) {
             Log.d(TAG, "adding overlay window to dream");
         }
 
-        mLayout = new ConstraintLayout(mContext);
-        mLayout.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-        mLayout.addOnAttachStateChangeListener(mRootViewAttachListener);
-        mWindow.setContentView(mLayout);
+        window.setContentView(mDreamOverlayComponent.getDreamOverlayContainerView());
+
+        mDreamOverlayContentView = mDreamOverlayComponent.getDreamOverlayContentView();
+        mDreamOverlayContentView.addOnAttachStateChangeListener(mRootViewAttachListener);
+
+        mDreamOverlayComponent.getDreamOverlayStatusBarViewController().init();
 
         final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
-        windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+        windowManager.addView(window.getDecorView(), window.getAttributes());
         mExecutor.execute(this::reloadOverlaysLocked);
     }
 
@@ -163,11 +182,11 @@
                 (view, layoutParams) -> {
                     // Always move UI related work to the main thread.
                     mExecutor.execute(() -> {
-                        if (mLayout == null) {
+                        if (mDreamOverlayContentView == null) {
                             return;
                         }
 
-                        mLayout.addView(view, layoutParams);
+                        mDreamOverlayContentView.addView(view, layoutParams);
                     });
                 },
                 () -> {
@@ -178,15 +197,6 @@
                 });
     }
 
-    @Inject
-    public DreamOverlayService(Context context, @Main Executor executor,
-            DreamOverlayStateController overlayStateController) {
-        mContext = context;
-        mExecutor = executor;
-        mStateController = overlayStateController;
-        mStateController.addCallback(mOverlayStateCallback);
-    }
-
     @Override
     public void onDestroy() {
         mStateController.removeCallback(mOverlayStateCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
new file mode 100644
index 0000000..9847ef6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -0,0 +1,86 @@
+/*
+ * 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.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+
+/**
+ * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
+ * dream. The status bar includes status icons such as battery and wifi.
+ */
+public class DreamOverlayStatusBarView extends ConstraintLayout implements
+        BatteryStateChangeCallback {
+
+    private BatteryMeterView mBatteryView;
+    private ImageView mWifiStatusView;
+
+    public DreamOverlayStatusBarView(Context context) {
+        this(context, null);
+    }
+
+    public DreamOverlayStatusBarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public DreamOverlayStatusBarView(
+            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mBatteryView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_battery),
+                "R.id.dream_overlay_battery must not be null");
+        mWifiStatusView = Preconditions.checkNotNull(findViewById(R.id.dream_overlay_wifi_status),
+                "R.id.dream_overlay_wifi_status must not be null");
+
+        mWifiStatusView.setImageDrawable(getContext().getDrawable(R.drawable.ic_signal_wifi_off));
+    }
+
+    /**
+     * Whether to show the battery percent text next to the battery status icons.
+     * @param show True if the battery percent text should be shown.
+     */
+    void showBatteryPercentText(boolean show) {
+        mBatteryView.setForceShowPercent(show);
+    }
+
+    /**
+     * Whether to show the wifi status icon.
+     * @param show True if the wifi status icon should be shown.
+     */
+    void showWifiStatus(boolean show) {
+        // Only show the wifi status icon when wifi isn't available.
+        mWifiStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
new file mode 100644
index 0000000..5674b9f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -0,0 +1,172 @@
+/*
+ * 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.dreams;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
+import com.android.systemui.dreams.dagger.DreamOverlayModule;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.ViewController;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/**
+ * View controller for {@link DreamOverlayStatusBarView}.
+ */
+@DreamOverlayComponent.DreamOverlayScope
+public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "WIFI_STATUS_" }, value = {
+            WIFI_STATUS_UNKNOWN,
+            WIFI_STATUS_UNAVAILABLE,
+            WIFI_STATUS_AVAILABLE
+    })
+    private @interface WifiStatus {}
+    private static final int WIFI_STATUS_UNKNOWN = 0;
+    private static final int WIFI_STATUS_UNAVAILABLE = 1;
+    private static final int WIFI_STATUS_AVAILABLE = 2;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "BATTERY_STATUS_" }, value = {
+            BATTERY_STATUS_UNKNOWN,
+            BATTERY_STATUS_NOT_CHARGING,
+            BATTERY_STATUS_CHARGING
+    })
+    private @interface BatteryStatus {}
+    private static final int BATTERY_STATUS_UNKNOWN = 0;
+    private static final int BATTERY_STATUS_NOT_CHARGING = 1;
+    private static final int BATTERY_STATUS_CHARGING = 2;
+
+    private final BatteryController mBatteryController;
+    private final BatteryMeterViewController mBatteryMeterViewController;
+    private final ConnectivityManager mConnectivityManager;
+    private final boolean mShowPercentAvailable;
+
+    private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
+            .clearCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
+
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onCapabilitiesChanged(
+                Network network, NetworkCapabilities networkCapabilities) {
+            onWifiAvailabilityChanged(
+                    networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            onWifiAvailabilityChanged(true);
+        }
+
+        @Override
+        public void onLost(Network network) {
+            onWifiAvailabilityChanged(false);
+        }
+    };
+
+    private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+            new BatteryController.BatteryStateChangeCallback() {
+                @Override
+                public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+                    DreamOverlayStatusBarViewController.this.onBatteryLevelChanged(charging);
+                }
+            };
+
+    private @WifiStatus int mWifiStatus = WIFI_STATUS_UNKNOWN;
+    private @BatteryStatus int mBatteryStatus = BATTERY_STATUS_UNKNOWN;
+
+    @Inject
+    public DreamOverlayStatusBarViewController(
+            Context context,
+            DreamOverlayStatusBarView view,
+            BatteryController batteryController,
+            @Named(DreamOverlayModule.DREAM_OVERLAY_BATTERY_CONTROLLER)
+                    BatteryMeterViewController batteryMeterViewController,
+            ConnectivityManager connectivityManager) {
+        super(view);
+        mBatteryController = batteryController;
+        mBatteryMeterViewController = batteryMeterViewController;
+        mConnectivityManager = connectivityManager;
+
+        mShowPercentAvailable = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_battery_percentage_setting_available);
+    }
+
+    @Override
+    protected void onInit() {
+        super.onInit();
+        mBatteryMeterViewController.init();
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mBatteryController.addCallback(mBatteryStateChangeCallback);
+        mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
+
+        NetworkCapabilities capabilities =
+                mConnectivityManager.getNetworkCapabilities(
+                        mConnectivityManager.getActiveNetwork());
+        onWifiAvailabilityChanged(
+                capabilities != null
+                        && capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI));
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mBatteryController.removeCallback(mBatteryStateChangeCallback);
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+    }
+
+    /**
+     * Wifi availability has changed. Update the wifi status icon as appropriate.
+     * @param available Whether wifi is available.
+     */
+    private void onWifiAvailabilityChanged(boolean available) {
+        final int newWifiStatus = available ? WIFI_STATUS_AVAILABLE : WIFI_STATUS_UNAVAILABLE;
+        if (mWifiStatus != newWifiStatus) {
+            mWifiStatus = newWifiStatus;
+            mView.showWifiStatus(mWifiStatus == WIFI_STATUS_UNAVAILABLE);
+        }
+    }
+
+    /**
+     * The battery level has changed. Update the battery status icon as appropriate.
+     * @param charging Whether the battery is currently charging.
+     */
+    private void onBatteryLevelChanged(boolean charging) {
+        final int newBatteryStatus =
+                charging ? BATTERY_STATUS_CHARGING : BATTERY_STATUS_NOT_CHARGING;
+        if (mBatteryStatus != newBatteryStatus) {
+            mBatteryStatus = newBatteryStatus;
+            mView.showBatteryPercentText(
+                    mBatteryStatus == BATTERY_STATUS_CHARGING && mShowPercentAvailable);
+        }
+    }
+}
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 7bf2361..ff5beb5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -21,8 +21,6 @@
 /**
  * Dagger Module providing Communal-related functionality.
  */
-@Module(subcomponents = {
-        AppWidgetOverlayComponent.class,
-})
+@Module(subcomponents = {AppWidgetOverlayComponent.class, DreamOverlayComponent.class})
 public interface DreamModule {
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
new file mode 100644
index 0000000..a3a446a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayComponent.java
@@ -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.android.systemui.dreams.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import android.view.ViewGroup;
+
+import com.android.systemui.dreams.DreamOverlayContainerView;
+import com.android.systemui.dreams.DreamOverlayStatusBarViewController;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+import dagger.Subcomponent;
+
+/**
+ * Dagger subcomponent for {@link DreamOverlayModule}.
+ */
+@Subcomponent(modules = {DreamOverlayModule.class})
+@DreamOverlayComponent.DreamOverlayScope
+public interface DreamOverlayComponent {
+    /** Simple factory for {@link DreamOverlayComponent}. */
+    @Subcomponent.Factory
+    interface Factory {
+        DreamOverlayComponent create();
+    }
+
+    /** Scope annotation for singleton items within the {@link DreamOverlayComponent}. */
+    @Documented
+    @Retention(RUNTIME)
+    @Scope
+    @interface DreamOverlayScope {}
+
+    /** Builds a {@link DreamOverlayContainerView} */
+    @DreamOverlayScope
+    DreamOverlayContainerView getDreamOverlayContainerView();
+
+    /** Builds a content view for dream overlays */
+    @DreamOverlayScope
+    ViewGroup getDreamOverlayContentView();
+
+    /** Builds a {@link DreamOverlayStatusBarViewController}. */
+    @DreamOverlayScope
+    DreamOverlayStatusBarViewController getDreamOverlayStatusBarViewController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
new file mode 100644
index 0000000..d0a8fad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -0,0 +1,105 @@
+/*
+ * 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.dreams.dagger;
+
+import android.content.ContentResolver;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.systemui.battery.BatteryMeterView;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayContainerView;
+import com.android.systemui.dreams.DreamOverlayStatusBarView;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.tuner.TunerService;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for {@link DreamOverlayComponent}. */
+@Module
+public abstract class DreamOverlayModule {
+    private static final String DREAM_OVERLAY_BATTERY_VIEW = "dream_overlay_battery_view";
+    public static final String DREAM_OVERLAY_BATTERY_CONTROLLER =
+            "dream_overlay_battery_controller";
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static DreamOverlayContainerView providesDreamOverlayContainerView(
+            LayoutInflater layoutInflater) {
+        return Preconditions.checkNotNull((DreamOverlayContainerView)
+                layoutInflater.inflate(R.layout.dream_overlay_container, null),
+                "R.layout.dream_layout_container could not be properly inflated");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static ViewGroup providesDreamOverlayContentView(DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_content),
+                "R.id.dream_overlay_content must not be null");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
+            DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
+                "R.id.status_bar must not be null");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    @Named(DREAM_OVERLAY_BATTERY_VIEW)
+    static BatteryMeterView providesBatteryMeterView(DreamOverlayContainerView view) {
+        return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_battery),
+                "R.id.battery must not be null");
+    }
+
+    /** */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    @Named(DREAM_OVERLAY_BATTERY_CONTROLLER)
+    static BatteryMeterViewController providesBatteryMeterViewController(
+            @Named(DREAM_OVERLAY_BATTERY_VIEW) BatteryMeterView batteryMeterView,
+            ConfigurationController configurationController,
+            TunerService tunerService,
+            BroadcastDispatcher broadcastDispatcher,
+            @Main Handler mainHandler,
+            ContentResolver contentResolver,
+            BatteryController batteryController) {
+        return new BatteryMeterViewController(
+                batteryMeterView,
+                configurationController,
+                tunerService,
+                broadcastDispatcher,
+                mainHandler,
+                contentResolver,
+                batteryController);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
new file mode 100644
index 0000000..96a90df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+/**
+ * Class to manage simple DeviceConfig-based feature flags.
+ *
+ * See [Flags] for instructions on defining new flags.
+ */
+interface FeatureFlags : FlagListenable {
+    /** Returns a boolean value for the given flag.  */
+    fun isEnabled(flag: BooleanFlag): Boolean
+
+    /** Returns a boolean value for the given flag.  */
+    fun isEnabled(flag: ResourceBooleanFlag): Boolean
+
+    /** Returns a string value for the given flag.  */
+    fun getString(flag: StringFlag): String
+
+    /** Returns a string value for the given flag.  */
+    fun getString(flag: ResourceStringFlag): String
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 0ee47a7..89623f4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -18,9 +18,11 @@
 
 import static com.android.systemui.flags.FlagManager.ACTION_GET_FLAGS;
 import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
-import static com.android.systemui.flags.FlagManager.FIELD_FLAGS;
-import static com.android.systemui.flags.FlagManager.FIELD_ID;
-import static com.android.systemui.flags.FlagManager.FIELD_VALUE;
+import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
+import static com.android.systemui.flags.FlagManager.EXTRA_ID;
+import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
+
+import static java.util.Objects.requireNonNull;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -30,8 +32,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
-import androidx.annotation.BoolRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
@@ -39,14 +41,13 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.util.settings.SecureSettings;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.function.Supplier;
 
 import javax.inject.Inject;
 
@@ -66,7 +67,9 @@
     private final FlagManager mFlagManager;
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
-    private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();
+    private final Supplier<Map<Integer, Flag<?>>> mFlagsCollector;
+    private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
+    private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
 
     @Inject
     public FeatureFlagsDebug(
@@ -74,100 +77,135 @@
             Context context,
             SecureSettings secureSettings,
             @Main Resources resources,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            @Nullable Supplier<Map<Integer, Flag<?>>> flagsCollector) {
         mFlagManager = flagManager;
         mSecureSettings = secureSettings;
         mResources = resources;
+        mFlagsCollector = flagsCollector != null ? flagsCollector : Flags::collectFlags;
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
         filter.addAction(ACTION_GET_FLAGS);
+        flagManager.setRestartAction(this::restartSystemUI);
+        flagManager.setClearCacheAction(this::removeFromCache);
         context.registerReceiver(mReceiver, filter, null, null);
         dumpManager.registerDumpable(TAG, this);
     }
 
     @Override
-    public boolean isEnabled(BooleanFlag flag) {
+    public boolean isEnabled(@NonNull BooleanFlag flag) {
         int id = flag.getId();
         if (!mBooleanFlagCache.containsKey(id)) {
-            boolean def = flag.getDefault();
-            if (flag.hasResourceOverride()) {
-                try {
-                    def = isEnabledInOverlay(flag.getResourceOverride());
-                } catch (Resources.NotFoundException e) {
-                    // no-op
-                }
-            }
-
-            mBooleanFlagCache.put(id, isEnabled(id, def));
+            mBooleanFlagCache.put(id,
+                    readFlagValue(id, flag.getDefault(), BooleanFlagSerializer.INSTANCE));
         }
 
         return mBooleanFlagCache.get(id);
     }
 
-    /** Return a {@link BooleanFlag}'s value. */
     @Override
-    public boolean isEnabled(int id, boolean defaultValue) {
-        Boolean result = isEnabledInternal(id);
+    public boolean isEnabled(@NonNull ResourceBooleanFlag flag) {
+        int id = flag.getId();
+        if (!mBooleanFlagCache.containsKey(id)) {
+            mBooleanFlagCache.put(id,
+                    readFlagValue(id, mResources.getBoolean(flag.getResourceId()),
+                            BooleanFlagSerializer.INSTANCE));
+        }
+
+        return mBooleanFlagCache.get(id);
+    }
+
+    @NonNull
+    @Override
+    public String getString(@NonNull StringFlag flag) {
+        int id = flag.getId();
+        if (!mStringFlagCache.containsKey(id)) {
+            mStringFlagCache.put(id,
+                    readFlagValue(id, flag.getDefault(), StringFlagSerializer.INSTANCE));
+        }
+
+        return mStringFlagCache.get(id);
+    }
+
+    @NonNull
+    @Override
+    public String getString(@NonNull ResourceStringFlag flag) {
+        int id = flag.getId();
+        if (!mStringFlagCache.containsKey(id)) {
+            mStringFlagCache.put(id,
+                    readFlagValue(id, mResources.getString(flag.getResourceId()),
+                            StringFlagSerializer.INSTANCE));
+        }
+
+        return mStringFlagCache.get(id);
+    }
+
+    @NonNull
+    private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+        requireNonNull(defaultValue, "defaultValue");
+        T result = readFlagValueInternal(id, serializer);
         return result == null ? defaultValue : result;
     }
 
+
     /** Returns the stored value or null if not set. */
-    private Boolean isEnabledInternal(int id) {
+    @Nullable
+    private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
         try {
-            return mFlagManager.isEnabled(id);
+            return mFlagManager.readFlagValue(id, serializer);
         } catch (Exception e) {
             eraseInternal(id);
         }
         return null;
     }
 
-    private boolean isEnabledInOverlay(@BoolRes int resId) {
-        return mResources.getBoolean(resId);
-    }
-
-    /** Set whether a given {@link BooleanFlag} is enabled or not. */
-    public void setEnabled(int id, boolean value) {
-        Boolean currentValue = isEnabledInternal(id);
-        if (currentValue != null && currentValue == value) {
+    private <T> void setFlagValue(int id, @NonNull T value, FlagSerializer<T> serializer) {
+        requireNonNull(value, "Cannot set a null value");
+        T currentValue = readFlagValueInternal(id, serializer);
+        if (Objects.equals(currentValue, value)) {
+            Log.i(TAG, "Flag id " + id + " is already " + value);
             return;
         }
-
-        JSONObject json = new JSONObject();
-        try {
-            json.put(FlagManager.FIELD_TYPE, FlagManager.TYPE_BOOLEAN);
-            json.put(FIELD_VALUE, value);
-            mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), json.toString());
-            Log.i(TAG, "Set id " + id + " to " + value);
-            restartSystemUI();
-        } catch (JSONException e) {
-            // no-op
+        final String data = serializer.toSettingsData(value);
+        if (data == null) {
+            Log.w(TAG, "Failed to set id " + id + " to " + value);
+            return;
         }
+        mSecureSettings.putString(mFlagManager.idToSettingsKey(id), data);
+        Log.i(TAG, "Set id " + id + " to " + value);
+        removeFromCache(id);
+        mFlagManager.dispatchListenersAndMaybeRestart(id);
     }
 
     /** Erase a flag's overridden value if there is one. */
     public void eraseFlag(int id) {
         eraseInternal(id);
-        restartSystemUI();
+        removeFromCache(id);
+        mFlagManager.dispatchListenersAndMaybeRestart(id);
     }
 
     /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
     private void eraseInternal(int id) {
         // We can't actually "erase" things from sysprops, but we can set them to empty!
-        mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), "");
+        mSecureSettings.putString(mFlagManager.idToSettingsKey(id), "");
         Log.i(TAG, "Erase id " + id);
     }
 
     @Override
-    public void addListener(Listener run) {
-        mFlagManager.addListener(run);
+    public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+        mFlagManager.addListener(flag, listener);
     }
 
     @Override
-    public void removeListener(Listener run) {
-        mFlagManager.removeListener(run);
+    public void removeListener(@NonNull Listener listener) {
+        mFlagManager.removeListener(listener);
     }
 
-    private void restartSystemUI() {
+    private void restartSystemUI(boolean requestSuppress) {
+        if (requestSuppress) {
+            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);
@@ -176,57 +214,103 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            String action = intent == null ? null : intent.getAction();
             if (action == null) {
                 return;
             }
             if (ACTION_SET_FLAG.equals(action)) {
                 handleSetFlag(intent.getExtras());
             } else if (ACTION_GET_FLAGS.equals(action)) {
-                Map<Integer, Flag<?>> knownFlagMap = Flags.collectFlags();
+                Map<Integer, Flag<?>> knownFlagMap = mFlagsCollector.get();
                 ArrayList<Flag<?>> flags = new ArrayList<>(knownFlagMap.values());
+
+                // Convert all flags to parcelable flags.
+                ArrayList<ParcelableFlag<?>> pFlags = new ArrayList<>();
+                for (Flag<?> f : flags) {
+                    ParcelableFlag<?> pf = toParcelableFlag(f);
+                    if (pf != null) {
+                        pFlags.add(pf);
+                    }
+                }
+
                 Bundle extras =  getResultExtras(true);
                 if (extras != null) {
-                    extras.putParcelableArrayList(FIELD_FLAGS, flags);
+                    extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
                 }
             }
         }
 
         private void handleSetFlag(Bundle extras) {
-            int id = extras.getInt(FIELD_ID);
+            if (extras == null) {
+                Log.w(TAG, "No extras");
+                return;
+            }
+            int id = extras.getInt(EXTRA_ID);
             if (id <= 0) {
                 Log.w(TAG, "ID not set or less than  or equal to 0: " + id);
                 return;
             }
 
-            Map<Integer, Flag<?>> flagMap = Flags.collectFlags();
+            Map<Integer, Flag<?>> flagMap = mFlagsCollector.get();
             if (!flagMap.containsKey(id)) {
                 Log.w(TAG, "Tried to set unknown id: " + id);
                 return;
             }
             Flag<?> flag = flagMap.get(id);
 
-            if (!extras.containsKey(FIELD_VALUE)) {
+            if (!extras.containsKey(EXTRA_VALUE)) {
                 eraseFlag(id);
                 return;
             }
 
-            if (flag instanceof BooleanFlag) {
-                setEnabled(id, extras.getBoolean(FIELD_VALUE));
+            Object value = extras.get(EXTRA_VALUE);
+            if (flag instanceof BooleanFlag && value instanceof Boolean) {
+                setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
+            } else  if (flag instanceof ResourceBooleanFlag && value instanceof Boolean) {
+                setFlagValue(id, (Boolean) value, BooleanFlagSerializer.INSTANCE);
+            } else if (flag instanceof StringFlag && value instanceof String) {
+                setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE);
+            } else if (flag instanceof ResourceStringFlag && value instanceof String) {
+                setFlagValue(id, (String) value, StringFlagSerializer.INSTANCE);
+            } else {
+                Log.w(TAG,
+                        "Unable to set " + id + " of type " + flag.getClass() + " to value of type "
+                                + (value == null ? null : value.getClass()));
             }
         }
+
+        /**
+         * Ensures that the data we send to the app reflects the current state of the flags.
+         *
+         * Also converts an non-parcelable versions of the flags to their parcelable versions.
+         */
+        @Nullable
+        private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
+            if (f instanceof BooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f));
+            }
+            if (f instanceof ResourceBooleanFlag) {
+                return new BooleanFlag(f.getId(), isEnabled((ResourceBooleanFlag) f));
+            }
+
+            // TODO: add support for other flag types.
+            Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
+            return null;
+        }
     };
 
+    private void removeFromCache(int id) {
+        mBooleanFlagCache.remove(id);
+        mStringFlagCache.remove(id);
+    }
+
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: true");
-        ArrayList<String> flagStrings = new ArrayList<>(mBooleanFlagCache.size());
-        for (Map.Entry<Integer, Boolean> entry : mBooleanFlagCache.entrySet()) {
-            flagStrings.add("  sysui_flag_" + entry.getKey() + ": " + entry.getValue());
-        }
-        flagStrings.sort(String.CASE_INSENSITIVE_ORDER);
-        for (String flagString : flagStrings) {
-            pw.println(flagString);
-        }
+        pw.println("booleans: " + mBooleanFlagCache.size());
+        mBooleanFlagCache.forEach((key, value) -> pw.println("  sysui_flag_" + key + ": " + value));
+        pw.println("Strings: " + mStringFlagCache.size());
+        mStringFlagCache.forEach((key, value) -> pw.println("  sysui_flag_" + key
+                + ": [length=" + value.length() + "] \"" + value + "\""));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index bd6cb66..348a8e2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,12 +16,17 @@
 
 package com.android.systemui.flags;
 
+import static java.util.Objects.requireNonNull;
+
+import android.content.res.Resources;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 
 import java.io.FileDescriptor;
@@ -37,17 +42,20 @@
  */
 @SysUISingleton
 public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
-    SparseBooleanArray mAccessedFlags = new SparseBooleanArray();
+    private final Resources mResources;
+    SparseBooleanArray mBooleanCache = new SparseBooleanArray();
+    SparseArray<String> mStringCache = new SparseArray<>();
     @Inject
-    public FeatureFlagsRelease(DumpManager dumpManager) {
+    public FeatureFlagsRelease(@Main Resources resources, DumpManager dumpManager) {
+        mResources = resources;
         dumpManager.registerDumpable("SysUIFlags", this);
     }
 
     @Override
-    public void addListener(Listener run) {}
+    public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
 
     @Override
-    public void removeListener(Listener run) {}
+    public void removeListener(@NonNull Listener listener) {}
 
     @Override
     public boolean isEnabled(BooleanFlag flag) {
@@ -55,18 +63,58 @@
     }
 
     @Override
-    public boolean isEnabled(int key, boolean defaultValue) {
-        mAccessedFlags.append(key, defaultValue);
+    public boolean isEnabled(ResourceBooleanFlag flag) {
+        int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
+        if (cacheIndex < 0) {
+            return isEnabled(flag.getId(), mResources.getBoolean(flag.getResourceId()));
+        }
+
+        return mBooleanCache.valueAt(cacheIndex);
+    }
+
+    private boolean isEnabled(int key, boolean defaultValue) {
+        mBooleanCache.append(key, defaultValue);
+        return defaultValue;
+    }
+
+    @NonNull
+    @Override
+    public String getString(@NonNull StringFlag flag) {
+        return getString(flag.getId(), flag.getDefault());
+    }
+
+    @NonNull
+    @Override
+    public String getString(@NonNull ResourceStringFlag flag) {
+        int cacheIndex = mStringCache.indexOfKey(flag.getId());
+        if (cacheIndex < 0) {
+            return getString(flag.getId(),
+                    requireNonNull(mResources.getString(flag.getResourceId())));
+        }
+
+        return mStringCache.valueAt(cacheIndex);
+    }
+
+    private String getString(int key, String defaultValue) {
+        mStringCache.append(key, defaultValue);
         return defaultValue;
     }
 
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("can override: false");
-        int size = mAccessedFlags.size();
-        for (int i = 0; i < size; i++) {
-            pw.println("  sysui_flag_" + mAccessedFlags.keyAt(i)
-                    + ": " + mAccessedFlags.valueAt(i));
+        int numBooleans = mBooleanCache.size();
+        pw.println("booleans: " + numBooleans);
+        for (int i = 0; i < numBooleans; i++) {
+            pw.println("  sysui_flag_" + mBooleanCache.keyAt(i) + ": " + mBooleanCache.valueAt(i));
+        }
+        int numStrings = mStringCache.size();
+        pw.println("Strings: " + numStrings);
+        for (int i = 0; i < numStrings; i++) {
+            final int id = mStringCache.keyAt(i);
+            final String value = mStringCache.valueAt(i);
+            final int length = value.length();
+            pw.println("  sysui_flag_" + id + ": [length=" + length + "] \"" + value + "\"");
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 458cdc1f..d9f6663 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -49,6 +49,8 @@
     public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
             new BooleanFlag(103, false);
 
+    public static final ResourceBooleanFlag NOTIFICATION_SHADE_DRAG =
+            new ResourceBooleanFlag(104, R.bool.config_enableNotificationShadeDrag);
 
     /***************************************/
     // 200 - keyguard/lockscreen
@@ -61,8 +63,8 @@
     public static final BooleanFlag NEW_UNLOCK_SWIPE_ANIMATION =
             new BooleanFlag(202, true);
 
-    public static final BooleanFlag CHARGING_RIPPLE =
-            new BooleanFlag(203, false, R.bool.flag_charging_ripple);
+    public static final ResourceBooleanFlag CHARGING_RIPPLE =
+            new ResourceBooleanFlag(203, R.bool.flag_charging_ripple);
 
     /***************************************/
     // 300 - power menu
@@ -77,8 +79,8 @@
     public static final BooleanFlag SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
             new BooleanFlag(401, false);
 
-    public static final BooleanFlag SMARTSPACE =
-            new BooleanFlag(402, false, R.bool.flag_smartspace);
+    public static final ResourceBooleanFlag SMARTSPACE =
+            new ResourceBooleanFlag(402, R.bool.flag_smartspace);
 
     /***************************************/
     // 500 - quick settings
@@ -88,11 +90,11 @@
     public static final BooleanFlag COMBINED_QS_HEADERS =
             new BooleanFlag(501, false);
 
-    public static final BooleanFlag PEOPLE_TILE =
-            new BooleanFlag(502, false, R.bool.flag_conversations);
+    public static final ResourceBooleanFlag PEOPLE_TILE =
+            new ResourceBooleanFlag(502, R.bool.flag_conversations);
 
-    public static final BooleanFlag QS_USER_DETAIL_SHORTCUT =
-            new BooleanFlag(503, false, R.bool.flag_lockscreen_qs_user_detail_shortcut);
+    public static final ResourceBooleanFlag QS_USER_DETAIL_SHORTCUT =
+            new ResourceBooleanFlag(503, R.bool.flag_lockscreen_qs_user_detail_shortcut);
 
     /***************************************/
     // 600- status bar
@@ -115,12 +117,13 @@
 
     /***************************************/
     // 800 - general visual/theme
-    public static final BooleanFlag MONET =
-            new BooleanFlag(800, true, R.bool.flag_monet);
+    public static final ResourceBooleanFlag MONET =
+            new ResourceBooleanFlag(800, R.bool.flag_monet);
 
     /***************************************/
     // 900 - media
     public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+    public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
 
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
index 10878dc..c46ffa0 100644
--- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
+++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java
@@ -108,7 +108,18 @@
         return p;
     }
 
-    public FragmentHostManager addTagListener(String tag, FragmentListener listener) {
+    /**
+     * Add a {@link FragmentListener} for a given tag
+     *
+     * @param tag string identifier for the fragment
+     * @param listener the listener to register
+     *
+     * @return this
+     */
+    public FragmentHostManager addTagListener(
+            @NonNull String tag,
+            @NonNull FragmentListener listener
+    ) {
         ArrayList<FragmentListener> listeners = mListeners.get(tag);
         if (listeners == null) {
             listeners = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index ff14064..2ebcd853 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -121,6 +121,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -236,6 +237,7 @@
     protected Handler mMainHandler;
     private int mSmallestScreenWidthDp;
     private final Optional<StatusBar> mStatusBarOptional;
+    private final SystemUIDialogManager mDialogManager;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
 
@@ -346,7 +348,8 @@
             PackageManager packageManager,
             Optional<StatusBar> statusBarOptional,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
-            DialogLaunchAnimator dialogLaunchAnimator) {
+            DialogLaunchAnimator dialogLaunchAnimator,
+            SystemUIDialogManager dialogManager) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -378,6 +381,7 @@
         mStatusBarOptional = statusBarOptional;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mDialogManager = dialogManager;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -677,7 +681,8 @@
                 mAdapter, mOverflowAdapter, mSysuiColorExtractor,
                 mStatusBarService, mNotificationShadeWindowController,
                 mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
-                mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils);
+                mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils,
+                mDialogManager);
 
         dialog.setOnDismissListener(this);
         dialog.setOnShowListener(this);
@@ -2219,10 +2224,12 @@
                 SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing,
                 MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
                 Optional<StatusBar> statusBarOptional,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
+                KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils,
+                SystemUIDialogManager systemUiDialogManager) {
             // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
             // dismiss this dialog when the device is locked.
-            super(context, themeRes, false /* dismissOnDeviceLock */);
+            super(context, themeRes, false /* dismissOnDeviceLock */,
+                    systemUiDialogManager);
             mContext = context;
             mAdapter = adapter;
             mOverflowAdapter = overflowAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index 729730c..7c0b93b 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.Size;
 
@@ -142,7 +143,8 @@
             mRefCount.incrementAndGet();
             synchronized (mRefCount) {
                 if (mBitmap == null) {
-                    mBitmap = mWallpaperManager.getBitmap(false /* hardware */);
+                    mBitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT,
+                            false /* hardware */);
                     mWcgContent = mWallpaperManager.wallpaperSupportsWcg(
                             WallpaperManager.FLAG_SYSTEM);
                     mWallpaperManager.forgetLoadedWallpaper();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index a801647..69bcf2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -24,6 +24,7 @@
 import android.view.RemoteAnimationTarget
 import android.view.SyncRtSurfaceTransactionApplier
 import android.view.View
+import androidx.annotation.VisibleForTesting
 import androidx.core.math.MathUtils
 import com.android.internal.R
 import com.android.keyguard.KeyguardClockSwitchController
@@ -33,6 +34,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
 import javax.inject.Inject
@@ -93,7 +95,8 @@
     keyguardViewMediator: Lazy<KeyguardViewMediator>,
     private val keyguardViewController: KeyguardViewController,
     private val smartspaceTransitionController: SmartspaceTransitionController,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
+    private val biometricUnlockController: BiometricUnlockController
 ) : KeyguardStateController.Callback {
 
     /**
@@ -107,7 +110,8 @@
      * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
      * animation is started in [notifyStartKeyguardExitAnimation].
      */
-    private var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
+    @VisibleForTesting
+    var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
     private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null
     private var surfaceBehindRemoteAnimationStartTime: Long = 0
 
@@ -134,7 +138,8 @@
      * Animator that animates in the surface behind the keyguard. This is used to play a canned
      * animation on the surface, if we're not doing a swipe gesture.
      */
-    private val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
+    @VisibleForTesting
+    val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)
 
     /** Rounded corner radius to apply to the surface behind the keyguard. */
     private var roundedCornerRadius = 0f
@@ -222,7 +227,18 @@
         // to animate it in. Otherwise, the swipe touch events will continue animating it.
         if (!requestedShowSurfaceBehindKeyguard) {
             keyguardViewController.hide(startTime, 350)
-            surfaceBehindEntryAnimator.start()
+
+            // If we're wake and unlocking, we don't want to animate the surface since we're going
+            // to do the light reveal scrim from the black AOD screen. Make it visible and end the
+            // remote aimation.
+            if (biometricUnlockController.isWakeAndUnlock) {
+                setSurfaceBehindAppearAmount(1f)
+                keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished(
+                    false /* cancelled */)
+            } else {
+                // Otherwise, animate it in normally.
+                surfaceBehindEntryAnimator.start()
+            }
         }
 
         // Finish the keyguard remote animation if the dismiss amount has crossed the threshold.
@@ -266,7 +282,7 @@
      * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is
      * cancelled).
      */
-    private fun setSurfaceBehindAppearAmount(amount: Float) {
+    fun setSurfaceBehindAppearAmount(amount: Float) {
         if (surfaceBehindRemoteAnimationTarget == null) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fbc9ba6..0512d48 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -117,11 +117,12 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -131,7 +132,6 @@
 import java.util.ArrayList;
 import java.util.Optional;
 import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import dagger.Lazy;
 
@@ -247,7 +247,7 @@
     private StatusBarManager mStatusBarManager;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final Executor mUiBgExecutor;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
     private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
 
     private boolean mSystemReady;
@@ -439,7 +439,7 @@
     private boolean mInGestureNavigationMode;
 
     private boolean mWakeAndUnlocking;
-    private IKeyguardDrawnCallback mDrawnCallback;
+    private Runnable mWakeAndUnlockingDrawnCallback;
     private CharSequence mCustomMessage;
 
     /**
@@ -817,7 +817,8 @@
     private DozeParameters mDozeParameters;
 
     private final Optional<UnfoldLightRevealOverlayAnimation> mUnfoldLightRevealAnimation;
-    private final AtomicInteger mPendingDrawnTasks = new AtomicInteger();
+    private final Optional<FoldAodAnimationController> mFoldAodAnimationController;
+    private final PendingDrawnTasksContainer mPendingDrawnTasks = new PendingDrawnTasksContainer();
 
     private final KeyguardStateController mKeyguardStateController;
     private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
@@ -846,7 +847,7 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationControllerLazy,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             InteractionJankMonitor interactionJankMonitor) {
         super(context);
@@ -877,14 +878,18 @@
                     mInGestureNavigationMode = QuickStepContract.isGesturalMode(mode);
                 }));
         mDozeParameters = dozeParameters;
-        mUnfoldLightRevealAnimation = unfoldComponent.map(
-                c -> c.getUnfoldLightRevealOverlayAnimation());
+
+        mUnfoldLightRevealAnimation = unfoldComponent
+                .map(SysUIUnfoldComponent::getUnfoldLightRevealOverlayAnimation);
+        mFoldAodAnimationController = unfoldComponent
+                .map(SysUIUnfoldComponent::getFoldAodAnimationController);
+
         mStatusBarStateController = statusBarStateController;
         statusBarStateController.addCallback(this);
 
         mKeyguardStateController = keyguardStateController;
         mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         mInteractionJankMonitor = interactionJankMonitor;
     }
 
@@ -1028,7 +1033,10 @@
                 if (!mExternallyEnabled) {
                     hideLocked();
                 }
-            } else if (mShowing) {
+            } else if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
+                // If we are going to sleep but the keyguard is showing (and will continue to be
+                // showing, not in the process of going away) then reset its state. Otherwise, let
+                // this fall through and explicitly re-lock the keyguard.
                 mPendingReset = true;
             } else if (
                     (offReason == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT
@@ -1069,7 +1077,7 @@
             mDeviceInteractive = false;
             mGoingToSleep = false;
             mWakeAndUnlocking = false;
-            mAnimatingScreenOff = mDozeParameters.shouldControlUnlockedScreenOff();
+            mAnimatingScreenOff = mDozeParameters.shouldAnimateDozingChange();
 
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
@@ -1112,7 +1120,7 @@
      * {@link #onStartedWakingUp} if the animation is cancelled.
      */
     public void maybeHandlePendingLock() {
-        if (mPendingLock && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+        if (mPendingLock && !mScreenOffAnimationController.isKeyguardShowDelayed()) {
             doKeyguardLocked(null);
             mPendingLock = false;
         }
@@ -2221,14 +2229,14 @@
             IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
             mKeyguardExitAnimationRunner = null;
 
-            if (mWakeAndUnlocking && mDrawnCallback != null) {
+            if (mWakeAndUnlocking && mWakeAndUnlockingDrawnCallback != null) {
 
                 // Hack level over 9000: To speed up wake-and-unlock sequence, force it to report
                 // the next draw from here so we don't have to wait for window manager to signal
                 // this to our ViewRootImpl.
                 mKeyguardViewControllerLazy.get().getViewRootImpl().setReportNextDraw();
-                notifyDrawn(mDrawnCallback);
-                mDrawnCallback = null;
+                mWakeAndUnlockingDrawnCallback.run();
+                mWakeAndUnlockingDrawnCallback = null;
             }
 
             LatencyTracker.getInstance(mContext)
@@ -2457,9 +2465,7 @@
 
         if (mSurfaceBehindRemoteAnimationFinishedCallback != null) {
             try {
-                if (!cancelled) {
-                    mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished();
-                }
+                mSurfaceBehindRemoteAnimationFinishedCallback.onAnimationFinished();
                 mSurfaceBehindRemoteAnimationFinishedCallback = null;
             } catch (RemoteException e) {
                 e.printStackTrace();
@@ -2566,31 +2572,27 @@
         synchronized (KeyguardViewMediator.this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenTurningOn");
 
-            if (mUnfoldLightRevealAnimation.isPresent()) {
-                mPendingDrawnTasks.set(2); // unfold overlay and keyguard drawn
+            mPendingDrawnTasks.reset();
 
+            if (mUnfoldLightRevealAnimation.isPresent()) {
                 mUnfoldLightRevealAnimation.get()
-                        .onScreenTurningOn(() -> {
-                            if (mPendingDrawnTasks.decrementAndGet() == 0) {
-                                try {
-                                    callback.onDrawn();
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Exception calling onDrawn():", e);
-                                }
-                            }
-                        });
-            } else {
-                mPendingDrawnTasks.set(1); // only keyguard drawn
+                        .onScreenTurningOn(mPendingDrawnTasks.registerTask("unfold-reveal"));
+            }
+
+            if (mFoldAodAnimationController.isPresent()) {
+                mFoldAodAnimationController.get()
+                        .onScreenTurningOn(mPendingDrawnTasks.registerTask("fold-to-aod"));
             }
 
             mKeyguardViewControllerLazy.get().onScreenTurningOn();
             if (callback != null) {
                 if (mWakeAndUnlocking) {
-                    mDrawnCallback = callback;
-                } else {
-                    notifyDrawn(callback);
+                    mWakeAndUnlockingDrawnCallback =
+                            mPendingDrawnTasks.registerTask("wake-and-unlocking");
                 }
             }
+
+            mPendingDrawnTasks.onTasksComplete(() -> notifyDrawn(callback));
         }
         Trace.endSection();
     }
@@ -2599,6 +2601,8 @@
         Trace.beginSection("KeyguardViewMediator#handleNotifyScreenTurnedOn");
         synchronized (this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOn");
+
+            mPendingDrawnTasks.reset();
             mKeyguardViewControllerLazy.get().onScreenTurnedOn();
         }
         Trace.endSection();
@@ -2607,18 +2611,18 @@
     private void handleNotifyScreenTurnedOff() {
         synchronized (this) {
             if (DEBUG) Log.d(TAG, "handleNotifyScreenTurnedOff");
-            mDrawnCallback = null;
+            mWakeAndUnlockingDrawnCallback = null;
         }
     }
 
     private void notifyDrawn(final IKeyguardDrawnCallback callback) {
         Trace.beginSection("KeyguardViewMediator#notifyDrawn");
-        if (mPendingDrawnTasks.decrementAndGet() == 0) {
-            try {
+        try {
+            if (callback != null) {
                 callback.onDrawn();
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Exception calling onDrawn():", e);
             }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Exception calling onDrawn():", e);
         }
         Trace.endSection();
     }
@@ -2777,9 +2781,9 @@
         pw.print("  mHideAnimationRun: "); pw.println(mHideAnimationRun);
         pw.print("  mPendingReset: "); pw.println(mPendingReset);
         pw.print("  mPendingLock: "); pw.println(mPendingLock);
-        pw.print("  mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.get());
+        pw.print("  mPendingDrawnTasks: "); pw.println(mPendingDrawnTasks.getPendingCount());
         pw.print("  mWakeAndUnlocking: "); pw.println(mWakeAndUnlocking);
-        pw.print("  mDrawnCallback: "); pw.println(mDrawnCallback);
+        pw.print("  mDrawnCallback: "); pw.println(mWakeAndUnlockingDrawnCallback);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt
new file mode 100644
index 0000000..a60033c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/PendingDrawnTasksContainer.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.keyguard
+
+import android.os.Trace
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicReference
+
+/**
+ * Allows to wait for multiple callbacks and notify when the last one is executed
+ */
+class PendingDrawnTasksContainer {
+
+    private var pendingDrawnTasksCount: AtomicInteger = AtomicInteger(0)
+    private var completionCallback: AtomicReference<Runnable> = AtomicReference()
+
+    /**
+     * Registers a task that we should wait for
+     * @return a runnable that should be invoked when the task is finished
+     */
+    fun registerTask(name: String): Runnable {
+        pendingDrawnTasksCount.incrementAndGet()
+
+        if (ENABLE_TRACE) {
+            Trace.beginAsyncSection("PendingDrawnTasksContainer#$name", 0)
+        }
+
+        return Runnable {
+            if (pendingDrawnTasksCount.decrementAndGet() == 0) {
+                val onComplete = completionCallback.getAndSet(null)
+                onComplete?.run()
+
+                if (ENABLE_TRACE) {
+                    Trace.endAsyncSection("PendingDrawnTasksContainer#$name", 0)
+                }
+            }
+        }
+    }
+
+    /**
+     * Clears state and initializes the container
+     */
+    fun reset() {
+        // Create new objects in case if there are pending callbacks from the previous invocations
+        completionCallback = AtomicReference()
+        pendingDrawnTasksCount = AtomicInteger(0)
+    }
+
+    /**
+     * Starts waiting for all tasks to be completed
+     * When all registered tasks complete it will invoke the [onComplete] callback
+     */
+    fun onTasksComplete(onComplete: Runnable) {
+        completionCallback.set(onComplete)
+
+        if (pendingDrawnTasksCount.get() == 0) {
+            val currentOnComplete = completionCallback.getAndSet(null)
+            currentOnComplete?.run()
+        }
+    }
+
+    /**
+     * Returns current pending tasks count
+     */
+    fun getPendingCount(): Int = pendingDrawnTasksCount.get()
+}
+
+private const val ENABLE_TRACE = false
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 8d23e9f..eecb55b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -46,8 +46,8 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
@@ -97,7 +97,7 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardStateController keyguardStateController,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             InteractionJankMonitor interactionJankMonitor) {
         return new KeyguardViewMediator(
@@ -121,7 +121,7 @@
                 statusBarStateController,
                 keyguardStateController,
                 keyguardUnlockAnimationController,
-                unlockedScreenOffAnimationController,
+                screenOffAnimationController,
                 notificationShadeDepthController,
                 interactionJankMonitor
         );
diff --git a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
index 9e00381..492fdc6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/LogBuffer.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.log
 
+import android.os.Trace
 import android.util.Log
 import com.android.systemui.log.dagger.LogModule
 import java.io.PrintWriter
@@ -176,7 +177,7 @@
         buffer.add(message as LogMessageImpl)
         if (logcatEchoTracker.isBufferLoggable(name, message.level) ||
                 logcatEchoTracker.isTagLoggable(message.tag, message.level)) {
-            echoToLogcat(message)
+            echo(message)
         }
     }
 
@@ -226,7 +227,7 @@
         pw.println(message.printer(message))
     }
 
-    private fun echoToLogcat(message: LogMessage) {
+    private fun echo(message: LogMessage) {
         val strMessage = message.printer(message)
         when (message.level) {
             LogLevel.VERBOSE -> Log.v(message.tag, strMessage)
@@ -236,6 +237,7 @@
             LogLevel.ERROR -> Log.e(message.tag, strMessage)
             LogLevel.WTF -> Log.wtf(message.tag, strMessage)
         }
+        Trace.instantForTrack(Trace.TRACE_TAG_APP, "UI Events", strMessage)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index b85f1072..e921ad29c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -862,8 +862,23 @@
 
 @VisibleForTesting
 internal object MediaPlayerData {
-    private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
-            emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+    private val EMPTY = MediaData(
+            userId = -1,
+            initialized = false,
+            backgroundColor = 0,
+            app = null,
+            appIcon = null,
+            artist = null,
+            song = null,
+            artwork = null,
+            actions = emptyList(),
+            actionsToShowInCompact = emptyList(),
+            packageName = "INVALID",
+            token = null,
+            clickIntent = null,
+            device = null,
+            active = true,
+            resumeAction = null)
     // Whether should prioritize Smartspace card.
     internal var shouldPrioritizeSs: Boolean = false
         private set
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f66eb5b..63555bb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -62,6 +62,7 @@
 import com.android.systemui.util.time.SystemClock;
 
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -122,6 +123,8 @@
     private MediaCarouselController mMediaCarouselController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final FalsingManager mFalsingManager;
+    private final MediaFlags mMediaFlags;
+
     // Used for swipe-to-dismiss logging.
     protected boolean mIsImpressed = false;
     private SystemClock mSystemClock;
@@ -138,7 +141,7 @@
             SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager,
             KeyguardDismissUtil keyguardDismissUtil, MediaOutputDialogFactory
             mediaOutputDialogFactory, MediaCarouselController mediaCarouselController,
-            FalsingManager falsingManager, SystemClock systemClock) {
+            FalsingManager falsingManager, MediaFlags mediaFlags, SystemClock systemClock) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mActivityStarter = activityStarter;
@@ -149,8 +152,8 @@
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mMediaCarouselController = mediaCarouselController;
         mFalsingManager = falsingManager;
+        mMediaFlags = mediaFlags;
         mSystemClock = systemClock;
-
         loadDimens();
 
         mSeekBarViewModel.setLogSmartspaceClick(() -> {
@@ -426,33 +429,61 @@
         deviceName.setText(deviceString);
         seamlessView.setContentDescription(deviceString);
 
-        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
-        // Media controls
-        int i = 0;
+        // Media action buttons
         List<MediaAction> actionIcons = data.getActions();
+        List<Integer> actionsWhenCollapsed = data.getActionsToShowInCompact();
+
+        if (mMediaFlags.areMediaSessionActionsEnabled() && data.getSemanticActions() != null) {
+            // Use PlaybackState actions instead
+            MediaButton semanticActions = data.getSemanticActions();
+
+            actionIcons = new ArrayList<MediaAction>();
+            actionIcons.add(semanticActions.getStartCustom());
+            actionIcons.add(semanticActions.getPrevOrCustom());
+            actionIcons.add(semanticActions.getPlayOrPause());
+            actionIcons.add(semanticActions.getNextOrCustom());
+            actionIcons.add(semanticActions.getEndCustom());
+
+            actionsWhenCollapsed = new ArrayList<Integer>();
+            actionsWhenCollapsed.add(1);
+            actionsWhenCollapsed.add(2);
+            actionsWhenCollapsed.add(3);
+        }
+
+        int i = 0;
         for (; i < actionIcons.size() && i < ACTION_IDS.length; i++) {
             int actionId = ACTION_IDS[i];
+            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
             final ImageButton button = mPlayerViewHolder.getAction(actionId);
             MediaAction mediaAction = actionIcons.get(i);
-            button.setImageIcon(mediaAction.getIcon());
-            button.setContentDescription(mediaAction.getContentDescription());
-            Runnable action = mediaAction.getAction();
+            if (mediaAction != null) {
+                button.setImageIcon(mediaAction.getIcon());
+                button.setContentDescription(mediaAction.getContentDescription());
+                Runnable action = mediaAction.getAction();
 
-            if (action == null) {
-                button.setEnabled(false);
+                if (action == null) {
+                    button.setEnabled(false);
+                } else {
+                    button.setEnabled(true);
+                    button.setOnClickListener(v -> {
+                        if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                            logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
+                                    /* isRecommendationCard */ false);
+                            action.run();
+                        }
+                    });
+                }
+                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+                setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
             } else {
-                button.setEnabled(true);
-                button.setOnClickListener(v -> {
-                    if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                        logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
-                                /* isRecommendationCard */ false);
-                        action.run();
-                    }
-                });
+                button.setImageIcon(null);
+                button.setContentDescription(null);
+                button.setEnabled(false);
+                setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
+                // for expanded layout, set as INVISIBLE so that we still reserve space in the UI
+                expandedSet.setVisibility(actionId, ConstraintSet.INVISIBLE);
+                expandedSet.setAlpha(actionId, 0.0f);
             }
-            boolean visibleInCompat = actionsWhenCollapsed.contains(i);
-            setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
-            setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
         }
 
         // Hide any unused buttons
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index bda07f4..4b8dfde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -47,7 +47,7 @@
      */
     val artwork: Icon?,
     /**
-     * List of actions that can be performed on the player: prev, next, play, pause, etc.
+     * List of generic action buttons for the media player, based on notification actions
      */
     val actions: List<MediaAction>,
     /**
@@ -55,6 +55,11 @@
      */
     val actionsToShowInCompact: List<Int>,
     /**
+     * Semantic actions buttons, based on the PlaybackState of the media session.
+     * If present, these actions will be preferred in the UI over [actions]
+     */
+    val semanticActions: MediaButton? = null,
+    /**
      * Package name of the app that's posting the media.
      */
     val packageName: String,
@@ -125,6 +130,32 @@
     }
 }
 
+/**
+ * Contains [MediaAction] objects which represent specific buttons in the UI
+ */
+data class MediaButton(
+    /**
+     * Play/pause button
+     */
+    var playOrPause: MediaAction? = null,
+    /**
+     * Next button, or custom action
+     */
+    var nextOrCustom: MediaAction? = null,
+    /**
+     * Previous button, or custom action
+     */
+    var prevOrCustom: MediaAction? = null,
+    /**
+     * First custom action space
+     */
+    var startCustom: MediaAction? = null,
+    /**
+     * Last custom action space
+     */
+    var endCustom: MediaAction? = null
+)
+
 /** State of a media action. */
 data class MediaAction(
     val icon: Icon?,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 7c0f7fc..d926e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -38,6 +38,7 @@
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.net.Uri
 import android.os.Parcelable
 import android.os.UserHandle
@@ -45,6 +46,7 @@
 import android.service.notification.StatusBarNotification
 import android.text.TextUtils
 import android.util.Log
+import androidx.media.utils.MediaConstants
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.Dumpable
 import com.android.systemui.R
@@ -80,8 +82,24 @@
 private const val DEBUG = true
 private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
 
-private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
-        emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+private val LOADING = MediaData(
+        userId = -1,
+        initialized = false,
+        backgroundColor = 0,
+        app = null,
+        appIcon = null,
+        artist = null,
+        song = null,
+        artwork = null,
+        actions = emptyList(),
+        actionsToShowInCompact = emptyList(),
+        packageName = "INVALID",
+        token = null,
+        clickIntent = null,
+        device = null,
+        active = true,
+        resumeAction = null)
+
 @VisibleForTesting
 internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
     "INVALID", null, emptyList(), null, 0, 0)
@@ -112,7 +130,8 @@
     private var useMediaResumption: Boolean,
     private val useQsMediaPlayer: Boolean,
     private val systemClock: SystemClock,
-    private val tunerService: TunerService
+    private val tunerService: TunerService,
+    private val mediaFlags: MediaFlags,
 ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     companion object {
@@ -127,6 +146,10 @@
         // Maximum number of actions allowed in compact view
         @JvmField
         val MAX_COMPACT_ACTIONS = 3
+
+        /** Maximum number of [PlaybackState.CustomAction] buttons supported */
+        @JvmField
+        val MAX_CUSTOM_ACTIONS = 4
     }
 
     private val themeText = com.android.settingslib.Utils.getColorAttr(context,
@@ -182,12 +205,13 @@
         activityStarter: ActivityStarter,
         smartspaceMediaDataProvider: SmartspaceMediaDataProvider,
         clock: SystemClock,
-        tunerService: TunerService
+        tunerService: TunerService,
+        mediaFlags: MediaFlags
     ) : this(context, backgroundExecutor, foregroundExecutor, mediaControllerFactory,
             broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
             mediaSessionBasedFilter, mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter,
             activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context),
-            Utils.useQsMediaPlayer(context), clock, tunerService)
+            Utils.useQsMediaPlayer(context), clock, tunerService, mediaFlags)
 
     private val appChangeReceiver = object : BroadcastReceiver() {
         override fun onReceive(context: Context, intent: Intent) {
@@ -522,7 +546,7 @@
         foregroundExecutor.execute {
             onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
                     null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
-                    packageName, token, appIntent, device = null, active = false,
+                    null, packageName, token, appIntent, device = null, active = false,
                     resumeAction = resumeAction, resumption = true, notificationKey = packageName,
                     hasCheckedForResume = true, lastActive = lastActive))
         }
@@ -594,15 +618,55 @@
         }
 
         // Control buttons
+        // If flag is enabled and controller has a PlaybackState, create actions from session info
+        // Otherwise, use the notification actions
+        var actionIcons: List<MediaAction> = emptyList()
+        var actionsToShowCollapsed: List<Int> = emptyList()
+        var semanticActions: MediaButton? = null
+        if (mediaFlags.areMediaSessionActionsEnabled() && mediaController.playbackState != null) {
+            semanticActions = createActionsFromState(sbn.packageName, mediaController)
+        } else {
+            val actions = createActionsFromNotification(sbn)
+            actionIcons = actions.first
+            actionsToShowCollapsed = actions.second
+        }
+
+        val playbackLocation =
+                if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
+                else if (mediaController.playbackInfo?.playbackType ==
+                        MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
+                else MediaData.PLAYBACK_CAST_LOCAL
+        val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
+        val lastActive = systemClock.elapsedRealtime()
+        foregroundExecutor.execute {
+            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
+            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
+            val active = mediaEntries[key]?.active ?: true
+            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
+                    smallIcon, artist, song, artWorkIcon, actionIcons, actionsToShowCollapsed,
+                    semanticActions, sbn.packageName, token, notif.contentIntent, null,
+                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
+                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
+                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
+                    lastActive = lastActive))
+        }
+    }
+
+    /**
+     * Generate action buttons based on notification actions
+     */
+    private fun createActionsFromNotification(sbn: StatusBarNotification):
+            Pair<List<MediaAction>, List<Int>> {
+        val notif = sbn.notification
         val actionIcons: MutableList<MediaAction> = ArrayList()
         val actions = notif.actions
         var actionsToShowCollapsed = notif.extras.getIntArray(
-                Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf<Int>()
+            Notification.EXTRA_COMPACT_ACTIONS)?.toMutableList() ?: mutableListOf()
         if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
-            Log.e(TAG, "Too many compact actions for $key, limiting to first $MAX_COMPACT_ACTIONS")
+            Log.e(TAG, "Too many compact actions for ${sbn.key}," +
+                "limiting to first $MAX_COMPACT_ACTIONS")
             actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
         }
-        // TODO: b/153736623 look into creating actions when this isn't a media style notification
 
         if (actions != null) {
             for ((index, action) in actions.withIndex()) {
@@ -631,32 +695,150 @@
                     action.getIcon()
                 }.setTint(themeText)
                 val mediaAction = MediaAction(
-                        mediaActionIcon,
-                        runnable,
-                        action.title)
+                    mediaActionIcon,
+                    runnable,
+                    action.title)
                 actionIcons.add(mediaAction)
             }
         }
+        return Pair(actionIcons, actionsToShowCollapsed)
+    }
 
-        val playbackLocation =
-                if (isRemoteCastNotification(sbn)) MediaData.PLAYBACK_CAST_REMOTE
-                else if (mediaController.playbackInfo?.playbackType ==
-                    MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL) MediaData.PLAYBACK_LOCAL
-                else MediaData.PLAYBACK_CAST_LOCAL
-        val isPlaying = mediaController.playbackState?.let { isPlayingState(it.state) } ?: null
-        val lastActive = systemClock.elapsedRealtime()
-        foregroundExecutor.execute {
-            val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
-            val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
-            val active = mediaEntries[key]?.active ?: true
-            onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
-                    smallIcon, artist, song, artWorkIcon, actionIcons,
-                    actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
-                    active, resumeAction = resumeAction, playbackLocation = playbackLocation,
-                    notificationKey = key, hasCheckedForResume = hasCheckedForResume,
-                    isPlaying = isPlaying, isClearable = sbn.isClearable(),
-                    lastActive = lastActive))
+    /**
+     * Generates action button info for this media session based on the PlaybackState
+     *
+     * @param packageName Package name for the media app
+     * @param controller MediaController for the current session
+     * @return a Pair consisting of a list of media actions, and a list of ints representing which
+     *      of those actions should be shown in the compact player
+     */
+    private fun createActionsFromState(packageName: String, controller: MediaController):
+            MediaButton? {
+        val actions = MediaButton()
+        controller.playbackState?.let { state ->
+            // First, check for standard actions
+            actions.playOrPause = if (isPlayingState(state.state)) {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PAUSE)
+            } else {
+                getStandardAction(controller, state.actions, PlaybackState.ACTION_PLAY)
+            }
+            val prevButton = getStandardAction(controller, state.actions,
+                    PlaybackState.ACTION_SKIP_TO_PREVIOUS)
+            val nextButton = getStandardAction(controller, state.actions,
+                    PlaybackState.ACTION_SKIP_TO_NEXT)
+
+            // Then, check for custom actions
+            val customActions = MutableList<MediaAction?>(MAX_CUSTOM_ACTIONS) { null }
+            var customCount = 0
+            for (i in 0..(MAX_CUSTOM_ACTIONS - 1)) {
+                getCustomAction(state, packageName, controller, customCount)?.let {
+                    customActions[customCount++] = it
+                }
+            }
+
+            // Finally, assign the remaining button slots: C A play/pause B D
+            // A = previous, else custom action (if not reserved)
+            // B = next, else custom action (if not reserved)
+            // C and D are always custom actions
+            val reservePrev = controller.extras?.getBoolean(
+                    MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV) == true
+            val reserveNext = controller.extras?.getBoolean(
+                    MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT) == true
+            var customIdx = 0
+
+            actions.prevOrCustom = if (prevButton != null) {
+                prevButton
+            } else if (!reservePrev) {
+                customActions[customIdx++]
+            } else {
+                null
+            }
+
+            actions.nextOrCustom = if (nextButton != null) {
+                nextButton
+            } else if (!reserveNext) {
+                customActions[customIdx++]
+            } else {
+                null
+            }
+
+            actions.startCustom = customActions[customIdx++]
+            actions.endCustom = customActions[customIdx++]
         }
+        return actions
+    }
+
+    /**
+     * Get a [MediaAction] representing one of
+     * - [PlaybackState.ACTION_PLAY]
+     * - [PlaybackState.ACTION_PAUSE]
+     * - [PlaybackState.ACTION_SKIP_TO_PREVIOUS]
+     * - [PlaybackState.ACTION_SKIP_TO_NEXT]
+     */
+    private fun getStandardAction(
+        controller: MediaController,
+        stateActions: Long,
+        action: Long
+    ): MediaAction? {
+        if (stateActions and action == 0L) {
+            return null
+        }
+
+        return when (action) {
+            PlaybackState.ACTION_PLAY -> {
+                MediaAction(
+                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_play),
+                    { controller.transportControls.play() },
+                    context.getString(R.string.controls_media_button_play)
+                )
+            }
+            PlaybackState.ACTION_PAUSE -> {
+                MediaAction(
+                    Icon.createWithResource(context,
+                        com.android.internal.R.drawable.ic_media_pause),
+                    { controller.transportControls.pause() },
+                    context.getString(R.string.controls_media_button_pause)
+                )
+            }
+            PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
+                MediaAction(
+                    Icon.createWithResource(context,
+                        com.android.internal.R.drawable.ic_media_previous),
+                    { controller.transportControls.skipToPrevious() },
+                    context.getString(R.string.controls_media_button_prev)
+                )
+            }
+            PlaybackState.ACTION_SKIP_TO_NEXT -> {
+                MediaAction(
+                    Icon.createWithResource(context, com.android.internal.R.drawable.ic_media_next),
+                    { controller.transportControls.skipToNext() },
+                    context.getString(R.string.controls_media_button_next)
+                )
+            }
+            else -> null
+        }
+    }
+
+    /**
+     * Get a [MediaAction] representing a [PlaybackState.CustomAction]
+     */
+    private fun getCustomAction(
+        state: PlaybackState,
+        packageName: String,
+        controller: MediaController,
+        index: Int
+    ): MediaAction? {
+        if (state.customActions.size <= index || state.customActions[index] == null) {
+            if (DEBUG) { Log.d(TAG, "not enough actions or action was null at $index") }
+            return null
+        }
+
+        val it = state.customActions[index]
+        return MediaAction(
+            Icon.createWithResource(packageName, it.icon),
+            { controller.transportControls.sendCustomAction(it, it.extras) },
+            it.name
+        )
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.kt
new file mode 100644
index 0000000..b4a4b42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaFlags.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.systemui.media
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+@SysUISingleton
+class MediaFlags @Inject constructor(private val featureFlags: FeatureFlags) {
+    /**
+     * Check whether media control actions should be based on PlaybackState instead of notification
+     */
+    fun areMediaSessionActionsEnabled(): Boolean {
+        return featureFlags.isEnabled(Flags.MEDIA_SESSION_ACTIONS)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index fb601e3..c8cd432 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator
 import android.annotation.IntDef
 import android.content.Context
+import android.content.res.Configuration
 import android.graphics.Rect
 import android.util.MathUtils
 import android.view.View
@@ -41,6 +42,7 @@
 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.util.Utils
 import com.android.systemui.util.animation.UniqueObjectHostView
 import javax.inject.Inject
 
@@ -186,6 +188,8 @@
     @MediaLocation
     private var currentAttachmentLocation = -1
 
+    private var inSplitShade = false
+
     /**
      * Is there any active media in the carousel?
      */
@@ -390,8 +394,9 @@
     init {
         updateConfiguration()
         configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
-            override fun onDensityOrFontScaleChanged() {
+            override fun onConfigChanged(newConfig: Configuration?) {
                 updateConfiguration()
+                updateDesiredLocation(forceNoAnimation = true, forceStateUpdate = true)
             }
         })
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
@@ -467,6 +472,7 @@
     private fun updateConfiguration() {
         distanceForFullShadeTransition = context.resources.getDimensionPixelSize(
                 R.dimen.lockscreen_shade_media_transition_distance)
+        inSplitShade = Utils.shouldUseSplitNotificationShade(context.resources)
     }
 
     /**
@@ -803,7 +809,7 @@
     private fun getQSTransformationProgress(): Float {
         val currentHost = getHost(desiredLocation)
         val previousHost = getHost(previousLocation)
-        if (hasActiveMedia && currentHost?.location == LOCATION_QS) {
+        if (hasActiveMedia && (currentHost?.location == LOCATION_QS && !inSplitShade)) {
             if (previousHost?.location == LOCATION_QQS) {
                 if (previousHost.visible || statusbarState != StatusBarState.KEYGUARD) {
                     return qsExpansion
@@ -934,7 +940,7 @@
                 statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
         val allowedOnLockscreen = notifLockscreenUserManager.shouldShowLockscreenNotifications()
         val location = when {
-            qsExpansion > 0.0f && !onLockscreen -> LOCATION_QS
+            (qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
             qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
             !hasActiveMedia -> LOCATION_QS
             onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
diff --git a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
index 142628c..c0f79d5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/RecommendationViewHolder.kt
@@ -21,7 +21,6 @@
 import android.view.ViewGroup
 import android.widget.ImageView
 import android.widget.TextView
-import androidx.annotation.IntegerRes
 import com.android.systemui.R
 import com.android.systemui.util.animation.TransitionLayout
 
@@ -47,14 +46,14 @@
         itemView.requireViewById(R.id.media_cover4_container),
         itemView.requireViewById(R.id.media_cover5_container),
         itemView.requireViewById(R.id.media_cover6_container))
-    val mediaCoverItemsResIds = listOf<@IntegerRes Int>(
+    val mediaCoverItemsResIds = listOf<Int>(
         R.id.media_cover1,
         R.id.media_cover2,
         R.id.media_cover3,
         R.id.media_cover4,
         R.id.media_cover5,
         R.id.media_cover6)
-    val mediaCoverContainersResIds = listOf<@IntegerRes Int>(
+    val mediaCoverContainersResIds = listOf<Int>(
         R.id.media_cover1_container,
         R.id.media_cover2_container,
         R.id.media_cover3_container,
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
index cf679f0..1174fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -20,15 +20,20 @@
 import android.view.WindowManager;
 
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
 import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.media.MediaHostStatesManager;
 import com.android.systemui.media.taptotransfer.MediaTttChipController;
+import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
 import com.android.systemui.media.taptotransfer.MediaTttFlags;
 import com.android.systemui.statusbar.commandline.CommandRegistry;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Optional;
+import java.util.concurrent.Executor;
 
 import javax.inject.Named;
 
@@ -78,11 +83,28 @@
     static Optional<MediaTttChipController> providesMediaTttChipController(
             MediaTttFlags mediaTttFlags,
             Context context,
-            CommandRegistry commandRegistry,
-            WindowManager windowManager) {
+            WindowManager windowManager,
+            @Main Executor mainExecutor,
+            @Background Executor backgroundExecutor) {
         if (!mediaTttFlags.isMediaTttEnabled()) {
             return Optional.empty();
         }
-        return Optional.of(new MediaTttChipController(commandRegistry, context, windowManager));
+        return Optional.of(new MediaTttChipController(
+                context, windowManager, mainExecutor, backgroundExecutor));
+    }
+
+    /** */
+    @Provides
+    @SysUISingleton
+    static Optional<MediaTttCommandLineHelper> providesMediaTttCommandLineHelper(
+            MediaTttFlags mediaTttFlags,
+            CommandRegistry commandRegistry,
+            MediaTttChipController mediaTttChipController,
+            @Main DelayableExecutor mainExecutor) {
+        if (!mediaTttFlags.isMediaTttEnabled()) {
+            return Optional.empty();
+        }
+        return Optional.of(new MediaTttCommandLineHelper(
+                commandRegistry, mediaTttChipController, mainExecutor));
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index 5fa66cd..8b19ccb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -113,6 +113,8 @@
             }
             mCheckBox.setVisibility(View.GONE);
             mStatusIcon.setVisibility(View.GONE);
+            mTitleText.setTextColor(Utils.getColorStateListDefaultColor(mContext,
+                    R.color.media_dialog_inactive_item_main_content));
             if (currentlyConnected && mController.isActiveRemoteDevice(device)
                     && mController.getSelectableMediaDevice().size() > 0) {
                 // Init active device layout
@@ -147,6 +149,8 @@
             } else {
                 // Set different layout for each device
                 if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
+                    mTitleText.setAlpha(DEVICE_CONNECTED_ALPHA);
+                    mTitleIcon.setAlpha(DEVICE_CONNECTED_ALPHA);
                     mStatusIcon.setImageDrawable(
                             mContext.getDrawable(R.drawable.media_output_status_failed));
                     setTwoLineLayout(device, false /* bFocused */,
@@ -157,6 +161,8 @@
                 } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
                     mStatusIcon.setImageDrawable(
                             mContext.getDrawable(R.drawable.media_output_status_check));
+                    mTitleText.setTextColor(Utils.getColorStateListDefaultColor(mContext,
+                            R.color.media_dialog_active_item_main_content));
                     setSingleLineLayout(getItemTitle(device), true /* bFocused */,
                             true /* showSeekBar */,
                             false /* showProgressBar */, true /* showStatus */);
@@ -172,6 +178,8 @@
         @Override
         void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
             if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
+                mTitleText.setTextColor(Utils.getColorStateListDefaultColor(mContext,
+                        R.color.media_dialog_inactive_item_main_content));
                 mCheckBox.setVisibility(View.GONE);
                 mAddIcon.setVisibility(View.GONE);
                 setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
@@ -182,6 +190,8 @@
                 mTitleIcon.setImageDrawable(d);
                 mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
             } else if (customizedItem == CUSTOMIZED_ITEM_DYNAMIC_GROUP) {
+                mTitleText.setTextColor(Utils.getColorStateListDefaultColor(mContext,
+                        R.color.media_dialog_active_item_main_content));
                 mConnectedItem = mContainerLayout;
                 mCheckBox.setVisibility(View.GONE);
                 if (mController.getSelectableMediaDevice().size() > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index dc4aaa9..bff792c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Typeface;
@@ -41,6 +40,7 @@
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.settingslib.Utils;
 import com.android.settingslib.media.MediaDevice;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
@@ -165,7 +165,7 @@
                 boolean showProgressBar, boolean showStatus) {
             mTwoLineLayout.setVisibility(View.GONE);
             final Drawable backgroundDrawable =
-                    showSeekBar
+                    showSeekBar || showProgressBar
                             ? mContext.getDrawable(R.drawable.media_output_item_background_active)
                                     .mutate() : mContext.getDrawable(
                             R.drawable.media_output_item_background)
@@ -177,15 +177,6 @@
             mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
             mTitleText.setText(title);
             mTitleText.setVisibility(View.VISIBLE);
-            mTitleText.setTranslationY(0);
-            if (bFocused) {
-                mTitleText.setTypeface(Typeface.create(mContext.getString(
-                        com.android.internal.R.string.config_headlineFontFamilyMedium),
-                        Typeface.NORMAL));
-            } else {
-                mTitleText.setTypeface(Typeface.create(mContext.getString(
-                        com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL));
-            }
         }
 
         void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -359,10 +350,10 @@
         Drawable getSpeakerDrawable() {
             final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
                     .mutate();
-            final ColorStateList list = mContext.getResources().getColorStateList(
-                    R.color.advanced_icon_color, mContext.getTheme());
-            drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
-                    PorterDuff.Mode.SRC_IN));
+            drawable.setColorFilter(
+                    new PorterDuffColorFilter(Utils.getColorStateListDefaultColor(mContext,
+                            R.color.media_dialog_active_item_main_content),
+                            PorterDuff.Mode.SRC_IN));
             return drawable;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index f7f9ee0..a9e9f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -45,6 +45,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Base dialog for media output UI
@@ -53,6 +54,7 @@
         MediaOutputController.Callback, Window.Callback {
 
     private static final String TAG = "MediaOutputDialog";
+    private static final String EMPTY_TITLE = " ";
 
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
     private final RecyclerView.LayoutManager mLayoutManager;
@@ -83,9 +85,12 @@
         }
     };
 
-    public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
-        super(context);
-        mContext = context;
+    public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController,
+            SystemUIDialogManager dialogManager) {
+        super(context, R.style.Theme_SystemUI_Dialog_Media, dialogManager);
+
+        // Save the context that is wrapped with our theme.
+        mContext = getContext();
         mMediaOutputController = mediaOutputController;
         mLayoutManager = new LinearLayoutManager(mContext);
         mListMaxHeight = context.getResources().getDimensionPixelSize(
@@ -106,6 +111,9 @@
         lp.setFitInsetsIgnoringVisibility(true);
         window.setAttributes(lp);
         window.setContentView(mDialogView);
+        // Sets window to a blank string to avoid talkback announce app label first when pop up,
+        // which doesn't make sense.
+        window.setTitle(EMPTY_TITLE);
 
         mHeaderTitle = mDialogView.requireViewById(R.id.header_title);
         mHeaderSubtitle = mDialogView.requireViewById(R.id.header_subtitle);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index eef5fb0..4eee60c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -16,11 +16,17 @@
 
 package com.android.systemui.media.dialog;
 
+import static android.provider.Settings.ACTION_BLUETOOTH_PAIRING_SETTINGS;
+
 import android.app.Notification;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.media.MediaMetadata;
@@ -31,6 +37,7 @@
 import android.media.session.PlaybackState;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -47,7 +54,6 @@
 import com.android.settingslib.media.InfoMediaManager;
 import com.android.settingslib.media.LocalMediaManager;
 import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputConstants;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.DialogLaunchAnimator;
@@ -55,6 +61,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -70,7 +77,8 @@
 
     private static final String TAG = "MediaOutputController";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
+    private static final String PAGE_CONNECTED_DEVICES_KEY =
+            "top_level_connected_devices";
     private final String mPackageName;
     private final Context mContext;
     private final MediaSessionManager mMediaSessionManager;
@@ -78,6 +86,7 @@
     private final ShadeController mShadeController;
     private final ActivityStarter mActivityStarter;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
+    private final SystemUIDialogManager mDialogManager;
     private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     private final boolean mAboveStatusbar;
     private final boolean mVolumeAdjustmentForRemoteGroupSessions;
@@ -99,7 +108,7 @@
             boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
             lbm, ShadeController shadeController, ActivityStarter starter,
             NotificationEntryManager notificationEntryManager, UiEventLogger uiEventLogger,
-            DialogLaunchAnimator dialogLaunchAnimator) {
+            DialogLaunchAnimator dialogLaunchAnimator, SystemUIDialogManager dialogManager) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -115,6 +124,7 @@
         mDialogLaunchAnimator = dialogLaunchAnimator;
         mVolumeAdjustmentForRemoteGroupSessions = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_volumeAdjustmentForRemoteGroupSessions);
+        mDialogManager = dialogManager;
     }
 
     void start(@NonNull Callback cb) {
@@ -247,9 +257,24 @@
             // Use default Bluetooth device icon to handle getIcon() is null case.
             drawable = mContext.getDrawable(com.android.internal.R.drawable.ic_bt_headphones_a2dp);
         }
+        if (!(drawable instanceof BitmapDrawable)) {
+            setColorFilter(drawable,
+                    mLocalMediaManager.getCurrentConnectedDevice().getId().equals(device.getId()));
+        }
         return BluetoothUtils.createIconWithDrawable(drawable);
     }
 
+    void setColorFilter(Drawable drawable, boolean isConnected) {
+        final ColorStateList list =
+                mContext.getResources().getColorStateList(
+                        !hasAdjustVolumeUserRestriction() && isConnected && !isTransferring()
+                                ? R.color.media_dialog_active_item_main_content
+                                : R.color.media_dialog_inactive_item_main_content,
+                        mContext.getTheme());
+        drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
+                PorterDuff.Mode.SRC_IN));
+    }
+
     IconCompat getNotificationIcon() {
         if (TextUtils.isEmpty(mPackageName)) {
             return null;
@@ -466,23 +491,34 @@
         mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
 
         mCallback.dismissDialog();
-        final ActivityStarter.OnDismissAction postKeyguardAction = () -> {
-            mContext.sendBroadcast(new Intent()
-                    .setAction(MediaOutputConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING)
-                    .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME));
-            mShadeController.animateCollapsePanels();
-            return true;
-        };
-        mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true);
+        Intent launchIntent =
+                new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        final Intent deepLinkIntent =
+                new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
+        if (deepLinkIntent.resolveActivity(mContext.getPackageManager()) != null) {
+            Log.d(TAG, "Device support split mode, launch page with deep link");
+            deepLinkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            deepLinkIntent.putExtra(
+                    Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
+                    launchIntent.toUri(Intent.URI_INTENT_SCHEME));
+            deepLinkIntent.putExtra(
+                    Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
+                    PAGE_CONNECTED_DEVICES_KEY);
+            mActivityStarter.startActivity(deepLinkIntent, true);
+            return;
+        }
+        mActivityStarter.startActivity(launchIntent, true);
     }
 
     void launchMediaOutputGroupDialog(View mediaOutputDialog) {
         // We show the output group dialog from the output dialog.
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mAboveStatusbar, mMediaSessionManager, mLocalBluetoothManager, mShadeController,
-                mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mActivityStarter, mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator,
+                mDialogManager);
         MediaOutputGroupDialog dialog = new MediaOutputGroupDialog(mContext, mAboveStatusbar,
-                controller);
+                controller, mDialogManager);
         mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 7696a1f..4e9da55 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -29,6 +29,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Dialog for media output transferring.
@@ -38,8 +39,9 @@
     final UiEventLogger mUiEventLogger;
 
     MediaOutputDialog(Context context, boolean aboveStatusbar, MediaOutputController
-            mediaOutputController, UiEventLogger uiEventLogger) {
-        super(context, mediaOutputController);
+            mediaOutputController, UiEventLogger uiEventLogger,
+            SystemUIDialogManager dialogManager) {
+        super(context, mediaOutputController, dialogManager);
         mUiEventLogger = uiEventLogger;
         mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
         if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index b91901d..a7bc852 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import javax.inject.Inject
 
 /**
@@ -38,7 +39,8 @@
     private val starter: ActivityStarter,
     private val notificationEntryManager: NotificationEntryManager,
     private val uiEventLogger: UiEventLogger,
-    private val dialogLaunchAnimator: DialogLaunchAnimator
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val dialogManager: SystemUIDialogManager
 ) {
     companion object {
         var mediaOutputDialog: MediaOutputDialog? = null
@@ -51,8 +53,9 @@
 
         val controller = MediaOutputController(context, packageName, aboveStatusBar,
             mediaSessionManager, lbm, shadeController, starter, notificationEntryManager,
-            uiEventLogger, dialogLaunchAnimator)
-        val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger)
+            uiEventLogger, dialogLaunchAnimator, dialogManager)
+        val dialog = MediaOutputDialog(context, aboveStatusBar, controller, uiEventLogger,
+                dialogManager)
         mediaOutputDialog = dialog
 
         // Show the dialog.
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
index f1c6601..9f752b9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -25,6 +25,7 @@
 import androidx.core.graphics.drawable.IconCompat;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 /**
  * Dialog for media output group.
@@ -33,8 +34,8 @@
 public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
 
     MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController
-            mediaOutputController) {
-        super(context, mediaOutputController);
+            mediaOutputController, SystemUIDialogManager dialogManager) {
+        super(context, mediaOutputController, dialogManager);
         mMediaOutputController.resetGroupMediaDevices();
         mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
         if (!aboveStatusbar) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
index af8fc0e..baa469d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipController.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.media.taptotransfer
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.PixelFormat
 import android.view.Gravity
@@ -24,15 +25,16 @@
 import android.view.WindowManager
 import android.widget.LinearLayout
 import android.widget.TextView
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.commandline.Command
-import com.android.systemui.statusbar.commandline.CommandRegistry
-import java.io.PrintWriter
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
+const val TAG = "MediaTapToTransfer"
+
 /**
  * A controller to display and hide the Media Tap-To-Transfer chip. This chip is shown when a user
  * is currently playing media on a local "media cast sender" device (e.g. a phone) and gets close
@@ -41,23 +43,20 @@
  */
 @SysUISingleton
 class MediaTttChipController @Inject constructor(
-    commandRegistry: CommandRegistry,
     private val context: Context,
     private val windowManager: WindowManager,
+    @Main private val mainExecutor: Executor,
+    @Background private val backgroundExecutor: Executor,
 ) {
-    init {
-        commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() }
-        commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() }
-    }
 
+    @SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
     private val windowLayoutParams = WindowManager.LayoutParams().apply {
         width = WindowManager.LayoutParams.WRAP_CONTENT
         height = WindowManager.LayoutParams.WRAP_CONTENT
         gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
         type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
+        flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
         title = "Media Tap-To-Transfer Chip View"
-        flags = (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
         format = PixelFormat.TRANSLUCENT
         setTrustedOverlay()
     }
@@ -65,7 +64,8 @@
     /** The chip view currently being displayed. Null if the chip is not being displayed. */
     private var chipView: LinearLayout? = null
 
-    private fun displayChip(chipType: ChipType, otherDeviceName: String) {
+    /** Displays the chip view for the given state. */
+    fun displayChip(chipState: MediaTttChipState) {
         val oldChipView = chipView
         if (chipView == null) {
             chipView = LayoutInflater
@@ -76,71 +76,68 @@
 
         // Text
         currentChipView.requireViewById<TextView>(R.id.text).apply {
-            text = context.getString(chipType.chipText, otherDeviceName)
+            text = context.getString(chipState.chipText, chipState.otherDeviceName)
         }
 
         // Loading
-        val showLoading = chipType == ChipType.TRANSFER_INITIATED
+        val showLoading = chipState is TransferInitiated
         currentChipView.requireViewById<View>(R.id.loading).visibility =
             if (showLoading) { View.VISIBLE } else { View.GONE }
 
         // Undo
-        val showUndo = chipType == ChipType.TRANSFER_SUCCEEDED
-        currentChipView.requireViewById<View>(R.id.undo).visibility =
-            if (showUndo) { View.VISIBLE } else { View.GONE }
+        val undoClickListener: View.OnClickListener? =
+            if (chipState is TransferSucceeded && chipState.undoRunnable != null)
+                View.OnClickListener { chipState.undoRunnable.run() }
+            else
+                null
+        val undoView = currentChipView.requireViewById<View>(R.id.undo)
+        undoView.visibility = if (undoClickListener != null) {
+            View.VISIBLE
+        } else {
+            View.GONE
+        }
+        undoView.setOnClickListener(undoClickListener)
 
+        // Future handling
+        if (chipState is TransferInitiated) {
+            addFutureCallback(chipState)
+        }
+
+        // Add view if necessary
         if (oldChipView == null) {
             windowManager.addView(chipView, windowLayoutParams)
         }
     }
 
-    private fun removeChip() {
+    /** Hides the chip. */
+    fun removeChip() {
         if (chipView == null) { return }
         windowManager.removeView(chipView)
         chipView = null
     }
 
-    @VisibleForTesting
-    enum class ChipType(
-        @StringRes internal val chipText: Int
-    ) {
-        MOVE_CLOSER_TO_TRANSFER(R.string.media_move_closer_to_transfer),
-        TRANSFER_INITIATED(R.string.media_transfer_playing),
-        TRANSFER_SUCCEEDED(R.string.media_transfer_playing),
-    }
-
-    inner class AddChipCommand : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-            val chipTypeArg = args[1]
-            ChipType.values().forEach {
-                if (it.name == chipTypeArg) {
-                    displayChip(it, otherDeviceName = args[0])
-                    return
+    /**
+     * Adds the appropriate callbacks to [chipState.future] so that we update the chip correctly
+     * when the future resolves.
+     */
+    private fun addFutureCallback(chipState: TransferInitiated) {
+        // Listen to the future on a background thread so we don't occupy the main thread while we
+        // wait for it to complete.
+        backgroundExecutor.execute {
+            try {
+                val undoRunnable = chipState.future.get(TRANSFER_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+                // Make UI changes on the main thread
+                mainExecutor.execute {
+                    displayChip(TransferSucceeded(chipState.otherDeviceName, undoRunnable))
+                }
+            } catch (ex: Exception) {
+                // TODO(b/203800327): Maybe show a failure chip here if UX decides we need one.
+                mainExecutor.execute {
+                    removeChip()
                 }
             }
-
-            pw.println("Chip type must be one of " +
-                    ChipType.values().map { it.name }.reduce { acc, s -> "$acc, $s" })
         }
-
-        override fun help(pw: PrintWriter) {
-            pw.println(
-                "Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG <deviceName> <chipType>"
-            )
-        }
-    }
-
-    inner class RemoveChipCommand : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) = removeChip()
-        override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
-        }
-    }
-
-    companion object {
-        @VisibleForTesting
-        const val ADD_CHIP_COMMAND_TAG = "media-ttt-chip-add"
-        @VisibleForTesting
-        const val REMOVE_CHIP_COMMAND_TAG = "media-ttt-chip-remove"
     }
 }
+
+private const val TRANSFER_TIMEOUT_SECONDS = 10L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
new file mode 100644
index 0000000..1f308a9a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttChipState.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.media.taptotransfer
+
+import androidx.annotation.StringRes
+import com.android.systemui.R
+import java.util.concurrent.Future
+
+/**
+ * A class that stores all the information necessary to display the media tap-to-transfer chip in
+ * certain states.
+ *
+ * This is a sealed class where each subclass represents a specific chip state. Each subclass can
+ * contain additional information that is necessary for only that state.
+ */
+sealed class MediaTttChipState(
+    /** A string resource for the text that the chip should display. */
+    @StringRes internal val chipText: Int,
+    /** The name of the other device involved in the transfer. */
+    internal val otherDeviceName: String
+)
+
+/**
+ * A state representing that the two devices are close but not close enough to initiate a transfer.
+ * The chip will instruct the user to move closer in order to initiate the transfer.
+ */
+class MoveCloserToTransfer(
+    otherDeviceName: String
+) : MediaTttChipState(R.string.media_move_closer_to_transfer, otherDeviceName)
+
+/**
+ * A state representing that a transfer has been initiated (but not completed).
+ *
+ * @property future a future that will be resolved when the transfer has either succeeded or failed.
+ *   If the transfer succeeded, the future can optionally return an undo runnable (see
+ *   [TransferSucceeded.undoRunnable]). [MediaTttChipController] is responsible for transitioning
+ *   the chip to the [TransferSucceeded] state if the future resolves successfully.
+ */
+class TransferInitiated(
+    otherDeviceName: String,
+    val future: Future<Runnable?>
+) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
+
+/**
+ * A state representing that a transfer has been successfully completed.
+ *
+ * @property undoRunnable if present, the runnable that should be run to undo the transfer. We will
+ *   show an Undo button on the chip if this runnable is present.
+ */
+class TransferSucceeded(
+    otherDeviceName: String,
+    val undoRunnable: Runnable? = null
+) : MediaTttChipState(R.string.media_transfer_playing, otherDeviceName)
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
new file mode 100644
index 0000000..663037c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.media.taptotransfer
+
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.PrintWriter
+import java.util.concurrent.FutureTask
+import javax.inject.Inject
+
+/**
+ * A helper class to test the media tap-to-transfer chip via the command line. See inner classes for
+ * command usages.
+ */
+@SysUISingleton
+class MediaTttCommandLineHelper @Inject constructor(
+    commandRegistry: CommandRegistry,
+    private val mediaTttChipController: MediaTttChipController,
+    @Main private val mainExecutor: DelayableExecutor,
+) {
+    init {
+        commandRegistry.registerCommand(ADD_CHIP_COMMAND_TAG) { AddChipCommand() }
+        commandRegistry.registerCommand(REMOVE_CHIP_COMMAND_TAG) { RemoveChipCommand() }
+    }
+
+    inner class AddChipCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            val otherDeviceName = args[0]
+            when (args[1]) {
+                MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME -> {
+                    mediaTttChipController.displayChip(MoveCloserToTransfer(otherDeviceName))
+                }
+                TRANSFER_INITIATED_COMMAND_NAME -> {
+                    val futureTask = FutureTask { fakeUndoRunnable }
+                    mediaTttChipController.displayChip(
+                        TransferInitiated(otherDeviceName, futureTask)
+                    )
+                    mainExecutor.executeDelayed({ futureTask.run() }, FUTURE_WAIT_TIME)
+
+                }
+                TRANSFER_SUCCEEDED_COMMAND_NAME -> {
+                    mediaTttChipController.displayChip(
+                        TransferSucceeded(otherDeviceName, fakeUndoRunnable)
+                    )
+                }
+                else -> {
+                    pw.println("Chip type must be one of " +
+                            "$MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME, " +
+                            "$TRANSFER_INITIATED_COMMAND_NAME, " +
+                            TRANSFER_SUCCEEDED_COMMAND_NAME
+                    )
+                }
+            }
+        }
+
+        override fun help(pw: PrintWriter) {
+            pw.println(
+                "Usage: adb shell cmd statusbar $ADD_CHIP_COMMAND_TAG <deviceName> <chipStatus>"
+            )
+        }
+    }
+
+    inner class RemoveChipCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+            mediaTttChipController.removeChip()
+        }
+        override fun help(pw: PrintWriter) {
+            pw.println("Usage: adb shell cmd statusbar $REMOVE_CHIP_COMMAND_TAG")
+        }
+    }
+
+    private val fakeUndoRunnable = Runnable {
+        Log.i(TAG, "Undo runnable triggered")
+    }
+}
+
+@VisibleForTesting
+const val ADD_CHIP_COMMAND_TAG = "media-ttt-chip-add"
+@VisibleForTesting
+const val REMOVE_CHIP_COMMAND_TAG = "media-ttt-chip-remove"
+@VisibleForTesting
+val MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME = MoveCloserToTransfer::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_INITIATED_COMMAND_NAME = TransferInitiated::class.simpleName!!
+@VisibleForTesting
+val TRANSFER_SUCCEEDED_COMMAND_NAME = TransferSucceeded::class.simpleName!!
+
+private const val FUTURE_WAIT_TIME = 2000L
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 8026df7..9c4227e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -627,7 +627,7 @@
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
         mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener);
-        mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener);
+        mPipOptional.ifPresent(mNavigationBarView::addPipExclusionBoundsChangeListener);
 
         prepareNavigationBarView();
         checkNavBarModes();
@@ -698,6 +698,7 @@
         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
         mHandler.removeCallbacks(mEnableLayoutTransitions);
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
+        mPipOptional.ifPresent(mNavigationBarView::removePipExclusionBoundsChangeListener);
         mFrame = null;
         mNavigationBarView = null;
         mOrientationHandle = null;
@@ -1251,6 +1252,7 @@
     private void onImeSwitcherClick(View v) {
         mInputMethodManager.showInputMethodPickerFromSystem(
                 true /* showAuxiliarySubtypes */, mDisplayId);
+        mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
     };
 
     private boolean onLongPressBackHome(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index bfabf71..a984974 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -59,9 +59,11 @@
 import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.pip.Pip;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
@@ -106,7 +108,8 @@
             NavigationBar.Factory navigationBarFactory,
             DumpManager dumpManager,
             AutoHideController autoHideController,
-            LightBarController lightBarController) {
+            LightBarController lightBarController,
+            Optional<Pip> pipOptional) {
         mContext = context;
         mHandler = mainHandler;
         mNavigationBarFactory = navigationBarFactory;
@@ -118,7 +121,7 @@
         mTaskbarDelegate = taskbarDelegate;
         mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService,
                 navBarHelper, navigationModeController, sysUiFlagsContainer,
-                dumpManager, autoHideController, lightBarController);
+                dumpManager, autoHideController, lightBarController, pipOptional);
         mIsTablet = isTablet(mContext);
         dumpManager.registerDumpable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 7c8c3e0..7adb7ac 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -75,12 +75,12 @@
 import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
 import com.android.systemui.navigationbar.buttons.RotationContextButton;
 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
-import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
+import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
 import com.android.systemui.shared.rotation.RotationButtonController;
-import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
@@ -1386,8 +1386,12 @@
         legacySplitScreen.registerInSplitScreenListener(mDockedListener);
     }
 
-    void registerPipExclusionBoundsChangeListener(Pip pip) {
-        pip.setPipExclusionBoundsChangeListener(mPipListener);
+    void addPipExclusionBoundsChangeListener(Pip pip) {
+        pip.addPipExclusionBoundsChangeListener(mPipListener);
+    }
+
+    void removePipExclusionBoundsChangeListener(Pip pip) {
+        pip.removePipExclusionBoundsChangeListener(mPipListener);
     }
 
     private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 68f4aea..feda99f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -40,6 +40,7 @@
 import android.content.ComponentCallbacks;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
@@ -68,9 +69,12 @@
 import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import com.android.wm.shell.pip.Pip;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -91,6 +95,7 @@
     private AutoHideController mAutoHideController;
     private LightBarController mLightBarController;
     private LightBarTransitionsController mLightBarTransitionsController;
+    private Optional<Pip> mPipOptional;
     private int mDisplayId;
     private int mNavigationIconHints;
     private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
@@ -113,6 +118,7 @@
     private Context mWindowContext;
     private ScreenPinningNotify mScreenPinningNotify;
     private int mNavigationMode;
+    private final Consumer<Rect> mPipListener;
 
     /**
      * Tracks the system calls for when taskbar should transiently show or hide so we can return
@@ -143,6 +149,7 @@
                 .create(context);
         mContext = context;
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
+        mPipListener = mEdgeBackGestureHandler::setPipStashExclusionBounds;
     }
 
     public void setDependencies(CommandQueue commandQueue,
@@ -151,7 +158,8 @@
             NavigationModeController navigationModeController,
             SysUiState sysUiState, DumpManager dumpManager,
             AutoHideController autoHideController,
-            LightBarController lightBarController) {
+            LightBarController lightBarController,
+            Optional<Pip> pipOptional) {
         // TODO: adding this in the ctor results in a dagger dependency cycle :(
         mCommandQueue = commandQueue;
         mOverviewProxyService = overviewProxyService;
@@ -162,6 +170,7 @@
         mAutoHideController = autoHideController;
         mLightBarController = lightBarController;
         mLightBarTransitionsController = createLightBarTransitionsController();
+        mPipOptional = pipOptional;
     }
 
     // Separated into a method to keep setDependencies() clean/readable.
@@ -207,6 +216,7 @@
         updateSysuiFlags();
         mAutoHideController.setNavigationBar(mAutoHideUiElement);
         mLightBarController.setNavigationBar(mLightBarTransitionsController);
+        mPipOptional.ifPresent(this::addPipExclusionBoundsChangeListener);
         mInitialized = true;
     }
 
@@ -228,9 +238,18 @@
         mAutoHideController.setNavigationBar(null);
         mLightBarTransitionsController.destroy(mContext);
         mLightBarController.setNavigationBar(null);
+        mPipOptional.ifPresent(this::removePipExclusionBoundsChangeListener);
         mInitialized = false;
     }
 
+    void addPipExclusionBoundsChangeListener(Pip pip) {
+        pip.addPipExclusionBoundsChangeListener(mPipListener);
+    }
+
+    void removePipExclusionBoundsChangeListener(Pip pip) {
+        pip.removePipExclusionBoundsChangeListener(mPipListener);
+    }
+
     /**
      * Returns {@code true} if this taskBar is {@link #init(int)}. Returns {@code false} if this
      * taskbar has not yet been {@link #init(int)} or has been {@link #destroy()}.
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 debd2eb..d27b716 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -96,6 +96,9 @@
         @UiEvent(doc = "The overview button was pressed in the navigation bar.")
         NAVBAR_OVERVIEW_BUTTON_TAP(535),
 
+        @UiEvent(doc = "The ime switcher button was pressed in the navigation bar.")
+        NAVBAR_IME_SWITCHER_BUTTON_TAP(923),
+
         @UiEvent(doc = "The home button was long-pressed in the navigation bar.")
         NAVBAR_HOME_BUTTON_LONGPRESS(536),
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index b533ac4..3e2da72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -113,6 +113,16 @@
     }
 
     @Override
+    public int getTilesHeight() {
+        // Use the first page as that is the maximum height we need to show.
+        TileLayout tileLayout = mPages.get(0);
+        if (tileLayout == null) {
+            return 0;
+        }
+        return tileLayout.getTilesHeight();
+    }
+
+    @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         // Pass configuration change to non-attached pages as well. Some config changes will cause
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 8588ddf..e230e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -27,7 +27,6 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
@@ -53,7 +52,6 @@
     private float mQsExpansion;
     private QSCustomizer mQSCustomizer;
     private NonInterceptingScrollView mQSPanelContainer;
-    private ImageView mDragHandle;
 
     private int mSideMargins;
     private boolean mQsDisabled;
@@ -71,7 +69,6 @@
         mQSDetail = findViewById(R.id.qs_detail);
         mHeader = findViewById(R.id.header);
         mQSCustomizer = findViewById(R.id.qs_customize);
-        mDragHandle = findViewById(R.id.qs_drag_handle);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
     }
 
@@ -190,23 +187,14 @@
         mQSDetail.setBottom(getTop() + scrollBottom);
         int qsDetailBottomMargin = ((MarginLayoutParams) mQSDetail.getLayoutParams()).bottomMargin;
         mQSDetail.setBottom(getTop() + scrollBottom - qsDetailBottomMargin);
-        // Pin the drag handle to the bottom of the panel.
-        mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight());
     }
 
     protected int calculateContainerHeight() {
         int heightOverride = mHeightOverride != -1 ? mHeightOverride : getMeasuredHeight();
         // Need to add the dragHandle height so touches will be intercepted by it.
-        int dragHandleHeight;
-        if (mDragHandle.getVisibility() == VISIBLE) {
-            dragHandleHeight = Math.round((1 - mQsExpansion) * mDragHandle.getHeight());
-        } else {
-            dragHandleHeight = 0;
-        }
         return mQSCustomizer.isCustomizing() ? mQSCustomizer.getHeight()
                 : Math.round(mQsExpansion * (heightOverride - mHeader.getHeight()))
-                + mHeader.getHeight()
-                + dragHandleHeight;
+                + mHeader.getHeight();
     }
 
     int calculateContainerBottom() {
@@ -221,8 +209,6 @@
     public void setExpansion(float expansion) {
         mQsExpansion = expansion;
         mQSPanelContainer.setScrollingEnabled(expansion > 0f);
-        mDragHandle.setAlpha(1.0f - expansion);
-        mDragHandle.setClickable(expansion == 0f); // Only clickable when fully collapsed
         updateExpansion();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
index dab0efe..e93c349 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -259,6 +259,12 @@
     }
 
     public static class Item {
+        public Item(int iconResId, CharSequence line1, Object tag) {
+            this.iconResId = iconResId;
+            this.line1 = line1;
+            this.tag = tag;
+        }
+
         public int iconResId;
         public QSTile.Icon icon;
         public Drawable overlay;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index ee59ae6..e82e9d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -33,7 +33,6 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout.LayoutParams;
-import android.widget.ImageView;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -94,7 +93,6 @@
     private float mLastPanelFraction;
     private float mSquishinessFraction = 1;
     private boolean mQsDisabled;
-    private ImageView mQsDragHandler;
 
     private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
     private final CommandQueue mCommandQueue;
@@ -205,7 +203,6 @@
         mHeader = view.findViewById(R.id.header);
         mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
         mFooter = qsFragmentComponent.getQSFooter();
-        mQsDragHandler = view.findViewById(R.id.qs_drag_handle);
 
         mQsDetailDisplayer.setQsPanelController(mQSPanelController);
 
@@ -249,11 +246,6 @@
                     mQSPanelController.getMediaHost().getHostView().setAlpha(1.0f);
                     mQSAnimator.requestAnimatorUpdate();
                 });
-
-        mQsDragHandler.setOnClickListener(v -> {
-            Log.d(TAG, "drag handler clicked");
-            mCommandQueue.animateExpandSettingsPanel(null);
-        });
     }
 
     @Override
@@ -385,30 +377,26 @@
     }
 
     private void updateQsState() {
-        final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
+        final boolean expanded = mQsExpanded || mInSplitShade;
+        final boolean expandVisually = expanded || mStackScrollerOverscrolling
                 || mHeaderAnimating;
-        mQSPanelController.setExpanded(mQsExpanded);
-        mQSDetail.setExpanded(mQsExpanded);
+        mQSPanelController.setExpanded(expanded);
+        mQSDetail.setExpanded(expanded);
         boolean keyguardShowing = isKeyguardState();
-        mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
+        mHeader.setVisibility((expanded || !keyguardShowing || mHeaderAnimating
                 || mShowCollapsedOnKeyguard)
                 ? View.VISIBLE
                 : View.INVISIBLE);
         mHeader.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (mQsExpanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
-        mFooter.setVisibility(!mQsDisabled && (mQsExpanded || !keyguardShowing || mHeaderAnimating
+                || (expanded && !mStackScrollerOverscrolling), mQuickQSPanelController);
+        mFooter.setVisibility(!mQsDisabled && (expanded || !keyguardShowing || mHeaderAnimating
                 || mShowCollapsedOnKeyguard)
                 ? View.VISIBLE
                 : View.INVISIBLE);
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
-                || (mQsExpanded && !mStackScrollerOverscrolling));
+                || (expanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(
                 !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
-        mQsDragHandler.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
-                || mShowCollapsedOnKeyguard)
-                && Utils.shouldUseSplitNotificationShade(getResources())
-                ? View.VISIBLE
-                : View.GONE);
     }
 
     private boolean isKeyguardState() {
@@ -418,7 +406,8 @@
     }
 
     private void updateShowCollapsedOnKeyguard() {
-        boolean showCollapsed = mBypassController.getBypassEnabled() || mTransitioningToFullShade;
+        boolean showCollapsed = mBypassController.getBypassEnabled()
+                || (mTransitioningToFullShade && !mInSplitShade);
         if (showCollapsed != mShowCollapsedOnKeyguard) {
             mShowCollapsedOnKeyguard = showCollapsed;
             updateQsState();
@@ -498,6 +487,8 @@
     public void setInSplitShade(boolean inSplitShade) {
         mInSplitShade = inSplitShade;
         mQSAnimator.setTranslateWhileExpanding(inSplitShade);
+        updateShowCollapsedOnKeyguard();
+        updateQsState();
     }
 
     @Override
@@ -516,7 +507,8 @@
     public void setQsExpansion(float expansion, float panelExpansionFraction,
             float proposedTranslation, float squishinessFraction) {
         float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
-        float progress = mTransitioningToFullShade ? mFullShadeProgress : panelExpansionFraction;
+        float progress = mTransitioningToFullShade || mState == StatusBarState.KEYGUARD
+                ? mFullShadeProgress : panelExpansionFraction;
         setAlphaAnimationProgress(mInSplitShade ? progress : 1);
         mContainer.setExpansion(expansion);
         final float translationScaleY = (mInSplitShade
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index d69deef..20c0fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -258,13 +258,8 @@
     }
 
     private void updateViewPositions() {
-        if (!(mTileLayout instanceof TileLayout)) {
-            return;
-        }
-        TileLayout layout = (TileLayout) mTileLayout;
-
         // Adjust view positions based on tile squishing
-        int tileHeightOffset = layout.getTilesHeight() - layout.getHeight();
+        int tileHeightOffset = mTileLayout.getTilesHeight() - mTileLayout.getHeight();
 
         boolean move = false;
         for (int i = 0; i < getChildCount(); i++) {
@@ -787,6 +782,12 @@
         /** */
         void setListening(boolean listening, UiEventLogger uiEventLogger);
 
+        /** */
+        int getHeight();
+
+        /** */
+        int getTilesHeight();
+
         /**
          * Sets a size modifier for the tile. Where 0 means collapsed, and 1 expanded.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index eddc206..d470fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -219,9 +219,8 @@
     }
 
     private void addTile(final QSTile tile, boolean collapsedView) {
-        final TileRecord r = new TileRecord();
-        r.tile = tile;
-        r.tileView = mHost.createTileView(getContext(), tile, collapsedView);
+        final TileRecord r =
+                new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
         mView.addTile(r);
         mRecords.add(r);
         mCachedSpecs = getTilesSpecs();
@@ -418,6 +417,11 @@
 
     /** */
     public static final class TileRecord extends QSPanel.Record {
+        public TileRecord(QSTile tile, com.android.systemui.plugins.qs.QSTileView tileView) {
+            this.tile = tile;
+            this.tileView = tileView;
+        }
+
         public QSTile tile;
         public com.android.systemui.plugins.qs.QSTileView tileView;
         public boolean scanState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 7f08e5b..bff318a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -275,6 +275,7 @@
         return Math.max(mColumns * mRows, 1);
     }
 
+    @Override
     public int getTilesHeight() {
         return mLastTileBottom + getPaddingBottom();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index b904505..9acd3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -56,6 +56,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 import javax.inject.Inject;
 
@@ -364,11 +365,13 @@
         }
         info.state.expandedAccessibilityClassName = "";
 
-        // The holder has a tileView, therefore this call is not null
-        holder.getTileAsCustomizeView().changeState(info.state);
-        holder.getTileAsCustomizeView().setShowAppLabel(position > mEditIndex && !info.isSystem);
+        CustomizeTileView tileView =
+                Objects.requireNonNull(
+                        holder.getTileAsCustomizeView(), "The holder must have a tileView");
+        tileView.changeState(info.state);
+        tileView.setShowAppLabel(position > mEditIndex && !info.isSystem);
         // Don't show the side view for third party tiles, as we don't have the actual state.
-        holder.getTileAsCustomizeView().setShowSideView(position < mEditIndex || info.isSystem);
+        tileView.setShowSideView(position < mEditIndex || info.isSystem);
         holder.mTileView.setSelected(true);
         holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
         holder.mTileView.setClickable(true);
@@ -448,7 +451,12 @@
         mFocusIndex = mEditIndex - 1;
         mNeedsFocus = true;
         if (mRecyclerView != null) {
-            mRecyclerView.post(() -> mRecyclerView.smoothScrollToPosition(mFocusIndex));
+            mRecyclerView.post(() -> {
+                final RecyclerView recyclerView = mRecyclerView;
+                if (recyclerView != null) {
+                    recyclerView.smoothScrollToPosition(mFocusIndex);
+                }
+            });
         }
         notifyDataSetChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 7e410d0..6c072f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -146,6 +146,10 @@
     }
 
     private static class TilePair {
+        private TilePair(QSTile tile) {
+            mTile = tile;
+        }
+
         QSTile mTile;
         boolean mReady = false;
     }
@@ -157,8 +161,7 @@
 
         TileCollector(List<QSTile> tilesToAdd, QSTileHost host) {
             for (QSTile tile: tilesToAdd) {
-                TilePair pair = new TilePair();
-                pair.mTile = tile;
+                TilePair pair = new TilePair(tile);
                 mQSTileList.add(pair);
             }
             mQSTileHost = host;
@@ -288,15 +291,11 @@
         if (mSpecs.contains(spec)) {
             return;
         }
-        TileInfo info = new TileInfo();
-        info.state = state;
-        info.state.dualTarget = false; // No dual targets in edit.
-        info.state.expandedAccessibilityClassName =
-                Button.class.getName();
-        info.spec = spec;
-        info.state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
+        state.dualTarget = false; // No dual targets in edit.
+        state.expandedAccessibilityClassName = Button.class.getName();
+        state.secondaryLabel = (isSystem || TextUtils.equals(state.label, appLabel))
                 ? null : appLabel;
-        info.isSystem = isSystem;
+        TileInfo info = new TileInfo(spec, state, isSystem);
         mTiles.add(info);
         mSpecs.add(spec);
     }
@@ -312,6 +311,12 @@
     }
 
     public static class TileInfo {
+        public TileInfo(String spec, QSTile.State state, boolean isSystem) {
+            this.spec = spec;
+            this.state = state;
+            this.isSystem = isSystem;
+        }
+
         public String spec;
         public QSTile.State state;
         public boolean isSystem;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 5bd02cc..10efec3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.Dependency;
@@ -562,7 +563,8 @@
             return this;
         }
 
-        CustomTile build() {
+        @VisibleForTesting
+        public CustomTile build() {
             if (mUserContext == null) {
                 throw new NullPointerException("UserContext cannot be null");
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 0f2db33..bfa2aaa4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -46,6 +46,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Objects;
 
 /**
  * Runs the day-to-day operations of which tiles should be bound and when.
@@ -178,6 +179,13 @@
                 return;
             }
             TileServiceManager service = mServices.get(customTile);
+            if (service == null) {
+                Log.e(
+                        TAG,
+                        "No TileServiceManager found in requestListening for tile "
+                                + customTile.getTileSpec());
+                return;
+            }
             if (!service.isActiveTile()) {
                 return;
             }
@@ -236,7 +244,7 @@
             verifyCaller(customTile);
             customTile.onDialogShown();
             mHost.forceCollapsePanels();
-            mServices.get(customTile).setShowingDialog(true);
+            Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(true);
         }
     }
 
@@ -245,7 +253,7 @@
         CustomTile customTile = getTileForToken(token);
         if (customTile != null) {
             verifyCaller(customTile);
-            mServices.get(customTile).setShowingDialog(false);
+            Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(false);
             customTile.onDialogHidden();
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index ac95bf5..c2a9e3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -169,7 +169,7 @@
         mFgsManagerTileProvider = fgsManagerTileProvider;
     }
 
-    public QSTile createTile(String tileSpec) {
+    public final QSTile createTile(String tileSpec) {
         QSTileImpl tile = createTileInternal(tileSpec);
         if (tile != null) {
             tile.initialize();
@@ -178,7 +178,7 @@
         return tile;
     }
 
-    private QSTileImpl createTileInternal(String tileSpec) {
+    protected QSTileImpl createTileInternal(String tileSpec) {
         // Stock tiles.
         switch (tileSpec) {
             case "wifi":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index b8ef312..7d8a28f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -18,6 +18,7 @@
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Looper;
+import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.service.quicksettings.Tile;
 import android.view.View;
@@ -119,7 +120,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
+        return new Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 65b6617..6fcfc3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -394,10 +394,11 @@
                 int count = 0;
                 for (CachedBluetoothDevice device : devices) {
                     if (mController.getBondState(device) == BluetoothDevice.BOND_NONE) continue;
-                    final Item item = new Item();
-                    item.iconResId = com.android.internal.R.drawable.ic_qs_bluetooth;
-                    item.line1 = device.getName();
-                    item.tag = device;
+                    final Item item =
+                            new Item(
+                                    com.android.internal.R.drawable.ic_qs_bluetooth,
+                                    device.getName(),
+                                    device);
                     int state = device.getMaxConnectionState();
                     if (state == BluetoothProfile.STATE_CONNECTED) {
                         item.iconResId = R.drawable.ic_bluetooth_connected;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index b83dc52..6e0ae29 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -78,7 +78,6 @@
     private final NetworkController mNetworkController;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final Callback mCallback = new Callback();
-    private Dialog mDialog;
     private boolean mWifiConnected;
     private boolean mHotspotConnected;
 
@@ -196,24 +195,36 @@
         showDetail(null /* view */);
     }
 
+    private static class DialogHolder {
+        private Dialog mDialog;
+
+        private void init(Dialog dialog) {
+            mDialog = dialog;
+        }
+    }
+
     private void showDetail(@Nullable View view) {
         mUiHandler.post(() -> {
-            mDialog = MediaRouteDialogPresenter.createDialog(mContext, ROUTE_TYPE_REMOTE_DISPLAY,
+            final DialogHolder holder = new DialogHolder();
+            final Dialog dialog = MediaRouteDialogPresenter.createDialog(
+                    mContext,
+                    ROUTE_TYPE_REMOTE_DISPLAY,
                     v -> {
                         mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
-                        mDialog.dismiss();
+                        holder.mDialog.dismiss();
                         mActivityStarter
                                 .postStartActivityDismissingKeyguard(getLongClickIntent(), 0);
                     });
-            SystemUIDialog.setShowForAllUsers(mDialog, true);
-            SystemUIDialog.registerDismissListener(mDialog);
-            SystemUIDialog.setWindowOnTop(mDialog);
+            holder.init(dialog);
+            SystemUIDialog.setShowForAllUsers(dialog, true);
+            SystemUIDialog.registerDismissListener(dialog);
+            SystemUIDialog.setWindowOnTop(dialog);
 
             mUiHandler.post(() -> {
                 if (view != null) {
-                    mDialogLaunchAnimator.showFromView(mDialog, view);
+                    mDialogLaunchAnimator.showFromView(dialog, view);
                 } else {
-                    mDialog.show();
+                    dialog.show();
                 }
             });
         });
@@ -402,11 +413,12 @@
                 // if we are connected, simply show that device
                 for (CastDevice device : devices) {
                     if (device.state == CastDevice.STATE_CONNECTED) {
-                        final Item item = new Item();
-                        item.iconResId = R.drawable.ic_cast_connected;
-                        item.line1 = getDeviceName(device);
+                        final Item item =
+                                new Item(
+                                        R.drawable.ic_cast_connected,
+                                        getDeviceName(device),
+                                        device);
                         item.line2 = mContext.getString(R.string.quick_settings_connected);
-                        item.tag = device;
                         item.canDisconnect = true;
                         items = new Item[] { item };
                         break;
@@ -422,13 +434,11 @@
                     for (String id : mVisibleOrder.keySet()) {
                         final CastDevice device = mVisibleOrder.get(id);
                         if (!devices.contains(device)) continue;
-                        final Item item = new Item();
-                        item.iconResId = R.drawable.ic_cast;
-                        item.line1 = getDeviceName(device);
+                        final Item item =
+                                new Item(R.drawable.ic_cast, getDeviceName(device), device);
                         if (device.state == CastDevice.STATE_CONNECTING) {
                             item.line2 = mContext.getString(R.string.quick_settings_connecting);
                         }
-                        item.tag = device;
                         items[i++] = item;
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7ba9cc2..a2577d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -98,7 +98,7 @@
                         toggleDataSaver();
                         Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
                     });
-            dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
+            dialog.setNeutralButton(com.android.internal.R.string.cancel, null);
             dialog.setShowForAllUsers(true);
 
             if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 83506b2..bb27458 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -190,7 +190,6 @@
                 case Settings.Secure.ZEN_DURATION_PROMPT:
                     mUiHandler.post(() -> {
                         Dialog dialog = makeZenModeDialog();
-                        SystemUIDialog.registerDismissListener(dialog);
                         if (view != null) {
                             mDialogLaunchAnimator.showFromView(dialog, view, false);
                         } else {
@@ -211,10 +210,12 @@
     }
 
     private Dialog makeZenModeDialog() {
-        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
-                .createDialog();
+        AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+                true /* cancelIsNeutral */).createDialog();
         SystemUIDialog.applyFlags(dialog);
         SystemUIDialog.setShowForAllUsers(dialog, true);
+        SystemUIDialog.registerDismissListener(dialog);
+        SystemUIDialog.setDialogSize(dialog);
         return dialog;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
index 75cf4d1..939a297 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FgsManagerTile.kt
@@ -54,7 +54,7 @@
     qsLogger: QSLogger?,
     private val fgsManagerDialogFactory: FgsManagerDialogFactory,
     private val runningFgsController: RunningFgsController
-) : QSTileImpl<QSTile.State?>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+) : QSTileImpl<QSTile.State>(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
         statusBarStateController, activityStarter, qsLogger), RunningFgsController.Callback {
 
     override fun handleInitialize() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index e79ca0c..da0069f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -378,26 +378,26 @@
 
         @Override
         public void onAccessPointsChanged(final List<WifiEntry> accessPoints) {
-            mAccessPoints = accessPoints.toArray(new WifiEntry[accessPoints.size()]);
-            filterUnreachableAPs();
+            mAccessPoints = filterUnreachableAPs(accessPoints);
 
             updateItems();
         }
 
         /** Filter unreachable APs from mAccessPoints */
-        private void filterUnreachableAPs() {
+        private WifiEntry[] filterUnreachableAPs(List<WifiEntry> unfiltered) {
             int numReachable = 0;
-            for (WifiEntry ap : mAccessPoints) {
+            for (WifiEntry ap : unfiltered) {
                 if (isWifiEntryReachable(ap)) numReachable++;
             }
-            if (numReachable != mAccessPoints.length) {
-                WifiEntry[] unfiltered = mAccessPoints;
-                mAccessPoints = new WifiEntry[numReachable];
+            if (numReachable != unfiltered.size()) {
+                WifiEntry[] accessPoints = new WifiEntry[numReachable];
                 int i = 0;
                 for (WifiEntry ap : unfiltered) {
-                    if (isWifiEntryReachable(ap)) mAccessPoints[i++] = ap;
+                    if (isWifiEntryReachable(ap)) accessPoints[i++] = ap;
                 }
+                return accessPoints;
             }
+            return unfiltered.toArray(new WifiEntry[0]);
         }
 
         @Override
@@ -454,10 +454,7 @@
                 items = new Item[mAccessPoints.length];
                 for (int i = 0; i < mAccessPoints.length; i++) {
                     final WifiEntry ap = mAccessPoints[i];
-                    final Item item = new Item();
-                    item.tag = ap;
-                    item.iconResId = mWifiController.getIcon(ap);
-                    item.line1 = ap.getSsid();
+                    final Item item = new Item(mWifiController.getIcon(ap), ap.getSsid(), ap);
                     item.line2 = ap.getSummary();
                     item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE
                             ? R.drawable.qs_ic_wifi_lock
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index 99eb5b6..544246e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -53,7 +53,10 @@
 
     private final InternetDialogController mInternetDialogController;
     private List<WifiEntry> mWifiEntries;
-    private int mWifiEntriesCount;
+    @VisibleForTesting
+    protected int mWifiEntriesCount;
+    @VisibleForTesting
+    protected int mMaxEntriesCount = InternetDialogController.MAX_WIFI_ENTRY_COUNT;
 
     protected View mHolderView;
     protected Context mContext;
@@ -87,7 +90,8 @@
      */
     public void setWifiEntries(@Nullable List<WifiEntry> wifiEntries, int wifiEntriesCount) {
         mWifiEntries = wifiEntries;
-        mWifiEntriesCount = wifiEntriesCount;
+        mWifiEntriesCount =
+                (wifiEntriesCount < mMaxEntriesCount) ? wifiEntriesCount : mMaxEntriesCount;
     }
 
     /**
@@ -101,6 +105,20 @@
     }
 
     /**
+     * Sets the maximum number of Wi-Fi networks.
+     */
+    public void setMaxEntriesCount(int count) {
+        if (count < 0 || mMaxEntriesCount == count) {
+            return;
+        }
+        mMaxEntriesCount = count;
+        if (mWifiEntriesCount > count) {
+            mWifiEntriesCount = count;
+            notifyDataSetChanged();
+        }
+    }
+
+    /**
      * ViewHolder for binding Wi-Fi view.
      */
     static class InternetViewHolder extends RecyclerView.ViewHolder {
@@ -133,7 +151,8 @@
         }
 
         void onBind(@NonNull WifiEntry wifiEntry) {
-            mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
+            mWifiIcon.setImageDrawable(
+                    getWifiDrawable(wifiEntry.getLevel(), wifiEntry.shouldShowXLevelIcon()));
             setWifiNetworkLayout(wifiEntry.getTitle(),
                     Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY));
 
@@ -170,12 +189,13 @@
             mWifiSummaryText.setText(summary);
         }
 
-        Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) {
-            if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+        Drawable getWifiDrawable(int level, boolean hasNoInternet) {
+            // If the Wi-Fi level is equal to WIFI_LEVEL_UNREACHABLE(-1), then a null drawable
+            // will be returned.
+            if (level == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
                 return null;
             }
-            final Drawable drawable = mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(),
-                    wifiEntry.getLevel());
+            final Drawable drawable = mWifiIconInjector.getIcon(hasNoInternet, level);
             if (drawable == null) {
                 return null;
             }
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 26c89ff..e7982bf 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
@@ -16,6 +16,7 @@
 package com.android.systemui.qs.tiles.dialog;
 
 import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
 
 import android.app.AlertDialog;
 import android.content.Context;
@@ -40,6 +41,7 @@
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
+import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -78,7 +80,8 @@
     private static final String TAG = "InternetDialog";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    static final long PROGRESS_DELAY_MS = 2000L;
+    static final long PROGRESS_DELAY_MS = 1500L;
+    static final int MAX_NETWORK_COUNT = 4;
 
     private final Handler mHandler;
     private final Executor mBackgroundExecutor;
@@ -124,8 +127,8 @@
     private Switch mMobileDataToggle;
     private View mMobileToggleDivider;
     private Switch mWiFiToggle;
-    private FrameLayout mDoneLayout;
-    private FrameLayout mAirplaneModeLayout;
+    private Button mDoneButton;
+    private Button mAirplaneModeButton;
     private Drawable mBackgroundOn;
     private Drawable mBackgroundOff = null;
     private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -137,6 +140,8 @@
     protected WifiEntry mConnectedWifiEntry;
     @VisibleForTesting
     protected int mWifiEntriesCount;
+    @VisibleForTesting
+    protected boolean mHasMoreWifiEntries;
 
     // Wi-Fi scanning progress bar
     protected boolean mIsProgressBarVisible;
@@ -157,7 +162,9 @@
         if (DEBUG) {
             Log.d(TAG, "Init InternetDialog");
         }
-        mContext = context;
+
+        // Save the context that is wrapped with our theme.
+        mContext = getContext();
         mHandler = handler;
         mBackgroundExecutor = executor;
         mInternetDialogFactory = internetDialogFactory;
@@ -211,8 +218,8 @@
         mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon);
         mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
         mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
-        mDoneLayout = mDialogView.requireViewById(R.id.done_layout);
-        mAirplaneModeLayout = mDialogView.requireViewById(R.id.apm_layout);
+        mDoneButton = mDialogView.requireViewById(R.id.done_button);
+        mAirplaneModeButton = mDialogView.requireViewById(R.id.apm_button);
         mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
         mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
         mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
@@ -234,7 +241,7 @@
 
         setOnClickListener();
         mTurnWifiOnLayout.setBackground(null);
-        mAirplaneModeLayout.setVisibility(
+        mAirplaneModeButton.setVisibility(
                 mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
         mWifiRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
         mWifiRecyclerView.setAdapter(mAdapter);
@@ -274,8 +281,8 @@
         mConnectedWifListLayout.setOnClickListener(null);
         mSeeAllLayout.setOnClickListener(null);
         mWiFiToggle.setOnCheckedChangeListener(null);
-        mDoneLayout.setOnClickListener(null);
-        mAirplaneModeLayout.setOnClickListener(null);
+        mDoneButton.setOnClickListener(null);
+        mAirplaneModeButton.setOnClickListener(null);
         mInternetDialogController.onStop();
         mInternetDialogFactory.destroyDialog();
     }
@@ -299,15 +306,11 @@
         if (DEBUG) {
             Log.d(TAG, "updateDialog");
         }
-        if (mInternetDialogController.isAirplaneModeEnabled()) {
-            mInternetDialogSubTitle.setVisibility(View.GONE);
-            mAirplaneModeLayout.setVisibility(View.VISIBLE);
-        } else {
-            mInternetDialogTitle.setText(getDialogTitleText());
-            mInternetDialogSubTitle.setVisibility(View.VISIBLE);
-            mInternetDialogSubTitle.setText(getSubtitleText());
-            mAirplaneModeLayout.setVisibility(View.GONE);
-        }
+        mInternetDialogTitle.setText(getDialogTitleText());
+        mInternetDialogSubTitle.setText(getSubtitleText());
+        mAirplaneModeButton.setVisibility(
+                mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
+
         updateEthernet();
         if (shouldUpdateMobileNetwork) {
             setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular(),
@@ -320,7 +323,7 @@
 
         showProgressBar();
         final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
-        final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+        final boolean isWifiEnabled = mWifiManager != null && mWifiManager.isWifiEnabled();
         final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
         updateWifiToggle(isWifiEnabled, isDeviceLocked);
         updateConnectedWifi(isWifiEnabled, isDeviceLocked);
@@ -350,11 +353,12 @@
         mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
         mWiFiToggle.setOnCheckedChangeListener(
                 (buttonView, isChecked) -> {
+                    if (mWifiManager == null) return;
                     buttonView.setChecked(isChecked);
                     mWifiManager.setWifiEnabled(isChecked);
                 });
-        mDoneLayout.setOnClickListener(v -> dismiss());
-        mAirplaneModeLayout.setOnClickListener(v -> {
+        mDoneButton.setOnClickListener(v -> dismiss());
+        mAirplaneModeButton.setOnClickListener(v -> {
             mInternetDialogController.setAirplaneModeDisabled();
         });
     }
@@ -375,8 +379,9 @@
             Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive);
         }
 
+        boolean isWifiEnabled = mWifiManager != null && mWifiManager.isWifiEnabled();
         if (!mInternetDialogController.hasActiveSubId()
-                && (!mWifiManager.isWifiEnabled() || !isCarrierNetworkActive)) {
+                && (!isWifiEnabled || !isCarrierNetworkActive)) {
             mMobileNetworkLayout.setVisibility(View.GONE);
         } else {
             mMobileNetworkLayout.setVisibility(View.VISIBLE);
@@ -462,22 +467,36 @@
             mSeeAllLayout.setVisibility(View.GONE);
             return;
         }
-        mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount());
+        final int wifiListMaxCount = getWifiListMaxCount();
+        if (mAdapter.getItemCount() > wifiListMaxCount) {
+            mHasMoreWifiEntries = true;
+        }
+        mAdapter.setMaxEntriesCount(wifiListMaxCount);
+        final int wifiListMinHeight = mWifiNetworkHeight * wifiListMaxCount;
+        if (mWifiRecyclerView.getMinimumHeight() != wifiListMinHeight) {
+            mWifiRecyclerView.setMinimumHeight(wifiListMinHeight);
+        }
         mWifiRecyclerView.setVisibility(View.VISIBLE);
-        final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0;
-        mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE);
+        mSeeAllLayout.setVisibility(mHasMoreWifiEntries ? View.VISIBLE : View.INVISIBLE);
     }
 
     @VisibleForTesting
     @MainThread
     int getWifiListMaxCount() {
-        int count = InternetDialogController.MAX_WIFI_ENTRY_COUNT;
+        // Use the maximum count of networks to calculate the remaining count for Wi-Fi networks.
+        int count = MAX_NETWORK_COUNT;
         if (mEthernetLayout.getVisibility() == View.VISIBLE) {
             count -= 1;
         }
         if (mMobileNetworkLayout.getVisibility() == View.VISIBLE) {
             count -= 1;
         }
+
+        // If the remaining count is greater than the maximum count of the Wi-Fi network, the
+        // maximum count of the Wi-Fi network is used.
+        if (count > MAX_WIFI_ENTRY_COUNT) {
+            count = MAX_WIFI_ENTRY_COUNT;
+        }
         if (mConnectedWifListLayout.getVisibility() == View.VISIBLE) {
             count -= 1;
         }
@@ -549,9 +568,13 @@
     }
 
     private void setProgressBarVisible(boolean visible) {
+        if (mIsProgressBarVisible == visible) {
+            return;
+        }
         mIsProgressBarVisible = visible;
-        mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
-        mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+        mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
+        mProgressBar.setIndeterminate(visible);
+        mDivider.setVisibility(visible ? View.GONE : View.VISIBLE);
         mInternetDialogSubTitle.setText(getSubtitleText());
     }
 
@@ -651,13 +674,14 @@
     @Override
     @WorkerThread
     public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
-            @Nullable WifiEntry connectedEntry) {
+            @Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries) {
         // Should update the carrier network layout when it is connected under airplane mode ON.
         boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE
                 && mInternetDialogController.isAirplaneModeEnabled();
         mHandler.post(() -> {
             mConnectedWifiEntry = connectedEntry;
             mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+            mHasMoreWifiEntries = hasMoreWifiEntries;
             updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */);
             mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
             mAdapter.notifyDataSetChanged();
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 706f423..3189d2f 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
@@ -18,6 +18,7 @@
 
 import static com.android.settingslib.mobile.MobileMappings.getIconKey;
 import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+import static com.android.wifitrackerlib.WifiEntry.CONNECTED_STATE_CONNECTED;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -86,9 +87,11 @@
 import com.android.wifitrackerlib.MergedCarrierEntry;
 import com.android.wifitrackerlib.WifiEntry;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicReference;
@@ -98,8 +101,10 @@
 
 import javax.inject.Inject;
 
-public class InternetDialogController implements WifiEntry.DisconnectCallback,
-        AccessPointController.AccessPointCallback {
+/**
+ * Controller for Internet Dialog.
+ */
+public class InternetDialogController implements AccessPointController.AccessPointCallback {
 
     private static final String TAG = "InternetDialogController";
     private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
@@ -122,7 +127,7 @@
             R.string.all_network_unavailable;
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    static final int MAX_WIFI_ENTRY_COUNT = 4;
+    static final int MAX_WIFI_ENTRY_COUNT = 3;
 
     private WifiManager mWifiManager;
     private Context mContext;
@@ -140,8 +145,6 @@
     private AccessPointController mAccessPointController;
     private IntentFilter mConnectionStateFilter;
     private InternetDialogCallback mCallback;
-    private WifiEntry mConnectedEntry;
-    private int mWifiEntriesCount;
     private UiEventLogger mUiEventLogger;
     private BroadcastDispatcher mBroadcastDispatcher;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -153,6 +156,7 @@
     private SignalDrawable mSignalDrawable;
     private LocationController mLocationController;
     private DialogLaunchAnimator mDialogLaunchAnimator;
+    private boolean mHasWifiEntries;
 
     @VisibleForTesting
     static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -174,6 +178,8 @@
     protected KeyguardStateController mKeyguardStateController;
     @VisibleForTesting
     protected boolean mHasEthernet = false;
+    @VisibleForTesting
+    protected ConnectedWifiInternetMonitor mConnectedWifiInternetMonitor;
 
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
             new KeyguardUpdateMonitorCallback() {
@@ -234,6 +240,7 @@
         mSignalDrawable = new SignalDrawable(mContext);
         mLocationController = locationController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mConnectedWifiInternetMonitor = new ConnectedWifiInternetMonitor();
     }
 
     void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -274,6 +281,7 @@
         mAccessPointController.removeAccessPointCallback(this);
         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
         mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
+        mConnectedWifiInternetMonitor.unregisterCallback();
     }
 
     @VisibleForTesting
@@ -313,15 +321,11 @@
     }
 
     CharSequence getSubtitleText(boolean isProgressBarVisible) {
-        if (isAirplaneModeEnabled()) {
-            return null;
-        }
-
         if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
-            // When the airplane mode is off and Wi-Fi is disabled.
+            // When Wi-Fi is disabled.
             //   Sub-Title: Wi-Fi is off
             if (DEBUG) {
-                Log.d(TAG, "Airplane mode off + Wi-Fi off.");
+                Log.d(TAG, "Wi-Fi off.");
             }
             return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
         }
@@ -335,7 +339,7 @@
             return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
         }
 
-        if (mConnectedEntry != null || mWifiEntriesCount > 0) {
+        if (mHasWifiEntries) {
             return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null;
         }
 
@@ -345,6 +349,10 @@
             return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
         }
 
+        if (isCarrierNetworkActive()) {
+            return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+        }
+
         // Sub-Title:
         // show non_carrier_network_unavailable
         //   - while Wi-Fi on + no Wi-Fi item
@@ -482,6 +490,11 @@
 
     private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
         class DisplayInfo {
+            DisplayInfo(SubscriptionInfo subscriptionInfo, CharSequence originalName) {
+                this.subscriptionInfo = subscriptionInfo;
+                this.originalName = originalName;
+            }
+
             public SubscriptionInfo subscriptionInfo;
             public CharSequence originalName;
             public CharSequence uniqueName;
@@ -495,12 +508,7 @@
                             // Filter out null values.
                             return (i != null && i.getDisplayName() != null);
                         })
-                        .map(i -> {
-                            DisplayInfo info = new DisplayInfo();
-                            info.subscriptionInfo = i;
-                            info.originalName = i.getDisplayName().toString().trim();
-                            return info;
-                        });
+                        .map(i -> new DisplayInfo(i, i.getDisplayName().toString().trim()));
 
         // A Unique set of display names
         Set<CharSequence> uniqueNames = new HashSet<>();
@@ -579,7 +587,7 @@
             return "";
         }
 
-        int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+        int resId = Objects.requireNonNull(mapIconSets(config).get(iconKey)).dataContentDescription;
         if (isCarrierNetworkActive()) {
             SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
                     TelephonyIcons.CARRIER_MERGED_WIFI;
@@ -823,7 +831,7 @@
         if (!mLocationController.isLocationEnabled()) {
             return false;
         }
-        return mWifiManager.isScanAlwaysAvailable();
+        return mWifiManager != null && mWifiManager.isScanAlwaysAvailable();
     }
 
     static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
@@ -872,59 +880,36 @@
             return;
         }
 
-        if (accessPoints == null || accessPoints.size() == 0) {
-            mConnectedEntry = null;
-            mWifiEntriesCount = 0;
-            if (mCallback != null) {
-                mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+        WifiEntry connectedEntry = null;
+        List<WifiEntry> wifiEntries = null;
+        final int accessPointsSize = (accessPoints == null) ? 0 : accessPoints.size();
+        final boolean hasMoreWifiEntries = (accessPointsSize > MAX_WIFI_ENTRY_COUNT);
+        if (accessPointsSize > 0) {
+            wifiEntries = new ArrayList<>();
+            final int count = hasMoreWifiEntries ? MAX_WIFI_ENTRY_COUNT : accessPointsSize;
+            mConnectedWifiInternetMonitor.unregisterCallback();
+            for (int i = 0; i < count; i++) {
+                WifiEntry entry = accessPoints.get(i);
+                mConnectedWifiInternetMonitor.registerCallbackIfNeed(entry);
+                if (connectedEntry == null && entry.isDefaultNetwork()
+                        && entry.hasInternetAccess()) {
+                    connectedEntry = entry;
+                } else {
+                    wifiEntries.add(entry);
+                }
             }
-            return;
+            mHasWifiEntries = true;
+        } else {
+            mHasWifiEntries = false;
         }
 
-        boolean hasConnectedWifi = false;
-        final int accessPointSize = accessPoints.size();
-        for (int i = 0; i < accessPointSize; i++) {
-            WifiEntry wifiEntry = accessPoints.get(i);
-            if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
-                mConnectedEntry = wifiEntry;
-                hasConnectedWifi = true;
-                break;
-            }
-        }
-        if (!hasConnectedWifi) {
-            mConnectedEntry = null;
-        }
-
-        int count = MAX_WIFI_ENTRY_COUNT;
-        if (mHasEthernet) {
-            count -= 1;
-        }
-        if (hasActiveSubId()) {
-            count -= 1;
-        }
-        if (hasConnectedWifi) {
-            count -= 1;
-        }
-        final List<WifiEntry> wifiEntries = accessPoints.stream()
-                .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
-                        || !wifiEntry.hasInternetAccess()))
-                .limit(count)
-                .collect(Collectors.toList());
-        mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
-
-        if (mCallback != null) {
-            mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
-        }
+        mCallback.onAccessPointsChanged(wifiEntries, connectedEntry, hasMoreWifiEntries);
     }
 
     @Override
     public void onSettingsActivityTriggered(Intent settingsIntent) {
     }
 
-    @Override
-    public void onDisconnectResult(int status) {
-    }
-
     private class InternetTelephonyCallback extends TelephonyCallback implements
             TelephonyCallback.DataConnectionStateListener,
             TelephonyCallback.DisplayInfoListener,
@@ -994,6 +979,55 @@
     }
 
     /**
+     * Helper class for monitoring the Internet access of the connected WifiEntry.
+     */
+    @VisibleForTesting
+    protected class ConnectedWifiInternetMonitor implements WifiEntry.WifiEntryCallback {
+
+        private WifiEntry mWifiEntry;
+
+        public void registerCallbackIfNeed(WifiEntry entry) {
+            if (entry == null || mWifiEntry != null) {
+                return;
+            }
+            // If the Wi-Fi is not connected yet, or it's the connected Wi-Fi with Internet
+            // access. Then we don't need to listen to the callback to update the Wi-Fi entries.
+            if (entry.getConnectedState() != CONNECTED_STATE_CONNECTED
+                    || (entry.isDefaultNetwork() && entry.hasInternetAccess())) {
+                return;
+            }
+            mWifiEntry = entry;
+            entry.setListener(this);
+        }
+
+        public void unregisterCallback() {
+            if (mWifiEntry == null) {
+                return;
+            }
+            mWifiEntry.setListener(null);
+            mWifiEntry = null;
+        }
+
+        @MainThread
+        @Override
+        public void onUpdated() {
+            if (mWifiEntry == null) {
+                return;
+            }
+            WifiEntry entry = mWifiEntry;
+            if (entry.getConnectedState() != CONNECTED_STATE_CONNECTED) {
+                unregisterCallback();
+                return;
+            }
+            if (entry.isDefaultNetwork() && entry.hasInternetAccess()) {
+                unregisterCallback();
+                // Trigger onAccessPointsChanged() to update the Wi-Fi entries.
+                scanWifiAccessPoints();
+            }
+        }
+    }
+
+    /**
      * Return {@code true} If the Ethernet exists
      */
     @MainThread
@@ -1068,7 +1102,7 @@
         void dismissDialog();
 
         void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
-                @Nullable WifiEntry connectedEntry);
+                @Nullable WifiEntry connectedEntry, boolean hasMoreWifiEntries);
     }
 
     void makeOverlayToast(int stringId) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 3ed7e84..60060aa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -76,6 +76,7 @@
 
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.internal.util.ScreenshotHelper;
 import com.android.systemui.Dumpable;
@@ -88,6 +89,7 @@
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 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.shared.recents.IOverviewProxy;
@@ -161,6 +163,7 @@
     private final Optional<StartingSurface> mStartingSurface;
     private final SmartspaceTransitionController mSmartspaceTransitionController;
     private final Optional<RecentTasks> mRecentTasks;
+    private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
 
@@ -248,6 +251,7 @@
             mContext.getSystemService(InputMethodManager.class)
                     .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */,
                             DEFAULT_DISPLAY);
+            mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
         }
 
         @Override
@@ -400,6 +404,13 @@
                     () -> mCommandQueue.handleSystemKey(KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN));
         }
 
+        @Override
+        public void toggleNotificationPanel() {
+            verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
+                    mStatusBarOptionalLazy.get().ifPresent(StatusBar::togglePanel));
+        }
+
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -560,6 +571,7 @@
             ShellTransitions shellTransitions,
             ScreenLifecycle screenLifecycle,
             SmartspaceTransitionController smartspaceTransitionController,
+            UiEventLogger uiEventLogger,
             DumpManager dumpManager) {
         super(broadcastDispatcher);
         mContext = context;
@@ -581,6 +593,7 @@
         mOneHandedOptional = oneHandedOptional;
         mShellTransitions = shellTransitions;
         mRecentTasks = recentTasks;
+        mUiEventLogger = uiEventLogger;
 
         // Assumes device always starts with back button until launcher tells it that it does not
         mNavBarButtonAlpha = 1.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 85bf98c..7f130cb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recents;
 
+import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
@@ -248,8 +249,8 @@
                     .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
             View buttons = mLayout.findViewById(R.id.screen_pinning_buttons);
             WindowManagerWrapper wm = WindowManagerWrapper.getInstance();
-            if (!QuickStepContract.isGesturalMode(mNavBarMode) 
-            	    && wm.hasSoftNavigationBar(mContext.getDisplayId())) {
+            if (!QuickStepContract.isGesturalMode(mNavBarMode)
+            	    && wm.hasSoftNavigationBar(mContext.getDisplayId()) && !isTablet(mContext)) {
                 buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
                 swapChildrenIfRtlAndVertical(buttons);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
index c50365f..71c5fad 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
@@ -15,7 +15,8 @@
 class SensorUseDialog(
     context: Context,
     val sensor: Int,
-    val clickListener: DialogInterface.OnClickListener
+    val clickListener: DialogInterface.OnClickListener,
+    val dismissListener: DialogInterface.OnDismissListener
 ) : SystemUIDialog(context) {
 
     // TODO move to onCreate (b/200815309)
@@ -69,6 +70,8 @@
                 context.getString(com.android.internal.R.string
                         .cancel), clickListener)
 
+        setOnDismissListener(dismissListener)
+
         setCancelable(false)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index b0071d9..dae375a 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -50,7 +50,7 @@
     private val keyguardStateController: KeyguardStateController,
     private val keyguardDismissUtil: KeyguardDismissUtil,
     @Background private val bgHandler: Handler
-) : Activity(), DialogInterface.OnClickListener {
+) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
 
     companion object {
         private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
@@ -120,7 +120,7 @@
             }
         }
 
-        mDialog = SensorUseDialog(this, sensor, this)
+        mDialog = SensorUseDialog(this, sensor, this, this)
         mDialog!!.show()
     }
 
@@ -212,4 +212,8 @@
                     .suppressSensorPrivacyReminders(sensor, suppressed)
         }
     }
+
+    override fun onDismiss(dialog: DialogInterface?) {
+        finish()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index f43d9c3..d7b4738 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -887,7 +887,13 @@
                 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
             }
         } else {
-            showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+            if (mKeyguardUpdateMonitor.isUdfpsSupported()
+                    && mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+                    KeyguardUpdateMonitor.getCurrentUser())) {
+                showBiometricMessage(mContext.getString(R.string.keyguard_unlock_press));
+            } else {
+                showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index dca7f70..491a175 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -174,7 +174,7 @@
     internal fun canDragDown(): Boolean {
         return (statusBarStateController.state == StatusBarState.KEYGUARD ||
                 nsslController.isInLockedDownShade()) &&
-                qS.isFullyCollapsed
+                (qS.isFullyCollapsed || useSplitShade)
     }
 
     /**
@@ -285,7 +285,7 @@
     internal val isDragDownAnywhereEnabled: Boolean
         get() = (statusBarStateController.getState() == StatusBarState.KEYGUARD &&
                 !keyguardBypassController.bypassEnabled &&
-                qS.isFullyCollapsed)
+                (qS.isFullyCollapsed || useSplitShade))
 
     /**
      * The amount in pixels that the user has dragged down.
@@ -298,8 +298,8 @@
                     nsslController.setTransitionToFullShadeAmount(field)
                     notificationPanelController.setTransitionToFullShadeAmount(field,
                             false /* animate */, 0 /* delay */)
-                    val progress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
-                    qS.setTransitionToFullShadeAmount(field, progress)
+                    dragProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
+                    qS.setTransitionToFullShadeAmount(field, dragProgress)
                     // TODO: appear media also in split shade
                     val mediaAmount = if (useSplitShade) 0f else field
                     mediaHierarchyManager.setTransitionToFullShadeAmount(mediaAmount)
@@ -308,6 +308,9 @@
             }
         }
 
+    var dragProgress = 0f
+        private set
+
     private fun transitionToShadeAmountCommon(dragDownAmount: Float) {
         val scrimProgress = MathUtils.saturate(dragDownAmount / scrimTransitionDistance)
         scrimController.setTransitionToFullShadeProgress(scrimProgress)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 5437ce6..d6125ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -25,7 +25,6 @@
 import android.app.NotificationManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -35,9 +34,13 @@
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.time.SystemClock;
 
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Executor;
 
 /**
  * This class handles listening to notification updates and passing them along to
@@ -47,23 +50,31 @@
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
     private static final boolean DEBUG = StatusBar.DEBUG;
+    private static final long MAX_RANKING_DELAY_MILLIS = 500L;
 
     private final Context mContext;
     private final NotificationManager mNotificationManager;
-    private final Handler mMainHandler;
+    private final SystemClock mSystemClock;
+    private final Executor mMainExecutor;
     private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
     private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
 
+    private final Deque<RankingMap> mRankingMapQueue = new ConcurrentLinkedDeque<>();
+    private final Runnable mDispatchRankingUpdateRunnable = this::dispatchRankingUpdate;
+    private long mSkippingRankingUpdatesSince = -1;
+
     /**
      * Injected constructor. See {@link StatusBarModule}.
      */
     public NotificationListener(
             Context context,
             NotificationManager notificationManager,
-            @Main Handler mainHandler) {
+            SystemClock systemClock,
+            @Main Executor mainExecutor) {
         mContext = context;
         mNotificationManager = notificationManager;
-        mMainHandler = mainHandler;
+        mSystemClock = systemClock;
+        mMainExecutor = mainExecutor;
     }
 
     /** Registers a listener that's notified when notifications are added/removed/etc. */
@@ -89,7 +100,7 @@
             return;
         }
         final RankingMap currentRanking = getCurrentRanking();
-        mMainHandler.post(() -> {
+        mMainExecutor.execute(() -> {
             // There's currently a race condition between the calls to getActiveNotifications() and
             // getCurrentRanking(). It's possible for the ranking that we store here to not contain
             // entries for every notification in getActiveNotifications(). To prevent downstream
@@ -119,7 +130,7 @@
             final RankingMap rankingMap) {
         if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
         if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-            mMainHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 processForRemoteInput(sbn.getNotification(), mContext);
 
                 for (NotificationHandler handler : mNotificationHandlers) {
@@ -134,7 +145,7 @@
             int reason) {
         if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
         if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
-            mMainHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 for (NotificationHandler handler : mNotificationHandlers) {
                     handler.onNotificationRemoved(sbn, rankingMap, reason);
                 }
@@ -151,12 +162,49 @@
     public void onNotificationRankingUpdate(final RankingMap rankingMap) {
         if (DEBUG) Log.d(TAG, "onRankingUpdate");
         if (rankingMap != null) {
+            // Add the ranking to the queue, then run dispatchRankingUpdate() on the main thread
             RankingMap r = onPluginRankingUpdate(rankingMap);
-            mMainHandler.post(() -> {
-                for (NotificationHandler handler : mNotificationHandlers) {
-                    handler.onNotificationRankingUpdate(r);
+            mRankingMapQueue.addLast(r);
+            // Maintaining our own queue and always posting the runnable allows us to guarantee the
+            //  relative ordering of all events which are dispatched, which is important so that the
+            //  RankingMap always has exactly the same elements that are current, per add/remove
+            //  events.
+            mMainExecutor.execute(mDispatchRankingUpdateRunnable);
+        }
+    }
+
+    /**
+     * This method is (and must be) the sole consumer of the RankingMap queue.  After pulling an
+     * object off the queue, it checks if the queue is empty, and only dispatches the ranking update
+     * if the queue is still empty.
+     */
+    private void dispatchRankingUpdate() {
+        if (DEBUG) Log.d(TAG, "dispatchRankingUpdate");
+        RankingMap r = mRankingMapQueue.pollFirst();
+        if (r == null) {
+            Log.wtf(TAG, "mRankingMapQueue was empty!");
+        }
+        if (!mRankingMapQueue.isEmpty()) {
+            final long now = mSystemClock.elapsedRealtime();
+            if (mSkippingRankingUpdatesSince == -1) {
+                mSkippingRankingUpdatesSince = now;
+            }
+            final long timeSkippingRankingUpdates = now - mSkippingRankingUpdatesSince;
+            if (timeSkippingRankingUpdates < MAX_RANKING_DELAY_MILLIS) {
+                if (DEBUG) {
+                    Log.d(TAG, "Skipping dispatch of onNotificationRankingUpdate() -- "
+                            + mRankingMapQueue.size() + " more updates already in the queue.");
                 }
-            });
+                return;
+            }
+            if (DEBUG) {
+                Log.d(TAG, "Proceeding with dispatch of onNotificationRankingUpdate() -- "
+                        + mRankingMapQueue.size() + " more updates already in the queue.");
+            }
+        }
+        mSkippingRankingUpdatesSince = -1;
+        for (NotificationHandler handler : mNotificationHandlers) {
+            handler.onNotificationRankingUpdate(r);
         }
     }
 
@@ -165,7 +213,7 @@
             String pkgName, UserHandle user, NotificationChannel channel, int modificationType) {
         if (DEBUG) Log.d(TAG, "onNotificationChannelModified");
         if (!onPluginNotificationChannelModified(pkgName, user, channel, modificationType)) {
-            mMainHandler.post(() -> {
+            mMainExecutor.execute(() -> {
                 for (NotificationHandler handler : mNotificationHandlers) {
                     handler.onNotificationChannelModified(pkgName, user, channel, modificationType);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
index c4fadff..4551807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
@@ -40,7 +40,7 @@
             super(context);
             setTitle(R.string.user_remove_user_title);
             setMessage(context.getString(R.string.user_remove_user_message));
-            setButton(DialogInterface.BUTTON_NEGATIVE,
+            setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(R.string.user_remove_user_remove), this);
@@ -51,7 +51,7 @@
 
         @Override
         public void onClick(DialogInterface dialog, int which) {
-            if (which == BUTTON_NEGATIVE) {
+            if (which == BUTTON_NEUTRAL) {
                 cancel();
             } else {
                 dismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
new file mode 100644
index 0000000..a1d086b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.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.statusbar.charging
+
+import android.graphics.Color
+import android.graphics.PointF
+import android.graphics.RuntimeShader
+import android.util.MathUtils
+
+/**
+ * Shader class that renders a distorted ripple for the UDFPS dwell effect.
+ * Adjustable shader parameters:
+ *   - progress
+ *   - origin
+ *   - color
+ *   - time
+ *   - maxRadius
+ *   - distortionStrength.
+ * See per field documentation for more details.
+ *
+ * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
+ */
+class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) {
+    companion object {
+        private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
+                uniform float in_time;
+                uniform float in_radius;
+                uniform float in_blur;
+                uniform vec4 in_color;
+                uniform float in_phase1;
+                uniform float in_phase2;
+                uniform float in_distortion_strength;"""
+        private const val SHADER_LIB = """
+                float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
+                  float blurHalf = blur * 0.5;
+                  float d = distance(uv, xy);
+                  return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
+                }
+
+                float softRing(vec2 uv, vec2 xy, float radius, float blur) {
+                  float thickness_half = radius * 0.25;
+                  float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
+                  float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
+                  return circle_outer - circle_inner;
+                }
+
+                vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) {
+                    return p + vec2(sin(p.x * frequency + in_phase1),
+                                    cos(p.y * frequency * 1.23 + in_phase2)) * distort_amount_xy;
+                }
+
+                vec4 ripple(vec2 p, float distort_xy, float frequency) {
+                    vec2 p_distorted = distort(p, in_time, distort_xy, frequency);
+                    float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
+                    float rippleAlpha = max(circle,
+                        softRing(p_distorted, in_origin, in_radius, in_blur)) * 0.25;
+                    return in_color * rippleAlpha;
+                }
+                """
+        private const val SHADER_MAIN = """vec4 main(vec2 p) {
+                    vec4 color1 = ripple(p,
+                        12 * in_distortion_strength, // distort_xy
+                        0.012 // frequency
+                    );
+                    vec4 color2 = ripple(p,
+                        17.5 * in_distortion_strength, // distort_xy
+                        0.018 // frequency
+                    );
+                    // Alpha blend between two layers.
+                    return vec4(color1.xyz + color2.xyz
+                        * (1 - color1.w), color1.w + color2.w * (1-color1.w));
+                }"""
+        private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
+    }
+
+    /**
+     * Maximum radius of the ripple.
+     */
+    var maxRadius: Float = 0.0f
+
+    /**
+     * Origin coordinate of the ripple.
+     */
+    var origin: PointF = PointF()
+        set(value) {
+            field = value
+            setUniform("in_origin", floatArrayOf(value.x, value.y))
+        }
+
+    /**
+     * Progress of the ripple. Float value between [0, 1].
+     */
+    var progress: Float = 0.0f
+        set(value) {
+            field = value
+            setUniform("in_radius",
+                    (1 - (1 - value) * (1 - value) * (1 - value))* maxRadius)
+            setUniform("in_blur", MathUtils.lerp(1f, 0.7f, value))
+        }
+
+    /**
+     * Distortion strength between [0, 1], with 0 being no distortion and 1 being full distortion.
+     */
+    var distortionStrength: Float = 0.0f
+        set(value) {
+            field = value
+            setUniform("in_distortion_strength", value)
+        }
+
+    /**
+     * Play time since the start of the effect in seconds.
+     */
+    var time: Float = 0.0f
+        set(value) {
+            field = value * 0.001f
+            setUniform("in_time", field)
+            setUniform("in_phase1", field * 2f + 0.367f)
+            setUniform("in_phase2", field * 5.2f * 1.531f)
+        }
+
+    /**
+     * A hex value representing the ripple color, in the format of ARGB
+     */
+    var color: Int = 0xffffff.toInt()
+        set(value) {
+            field = value
+            val color = Color.valueOf(value)
+            setUniform("in_color", floatArrayOf(color.red(),
+                    color.green(), color.blue(), color.alpha()))
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index fa99353..842be5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -94,9 +94,8 @@
                 nowPluggedIn: Boolean,
                 charging: Boolean
             ) {
-                // Suppresses the ripple when it's disabled, or when the state change comes
-                // from wireless charging.
-                if (!rippleEnabled || batteryController.isPluggedInWireless) {
+                // Suppresses the ripple when the state change comes from wireless charging.
+                if (batteryController.isPluggedInWireless) {
                     return
                 }
                 val wasPluggedIn = pluggedIn
@@ -146,7 +145,7 @@
     }
 
     fun startRipple() {
-        if (!rippleEnabled || 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/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 7bf710c..c9ce12a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -117,6 +117,7 @@
     }
 
     public boolean canConfigWifi() {
+        if (!mWifiPickerTrackerFactory.isSupported()) return false;
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
                 new UserHandle(mCurrentUser));
     }
@@ -312,6 +313,10 @@
             mWorkerHandler = workerHandler;
         }
 
+        private boolean isSupported() {
+            return mWifiManager != null;
+        }
+
         /**
          * Create a {@link WifiPickerTracker}
          *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
index f8f7b7f..89fe24f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java
@@ -82,7 +82,7 @@
     @Override
     public void notifyListeners(SignalCallback callback) {
         if (mCurrentState.isCarrierMerged) {
-            if (mCurrentState.isDefault) {
+            if (mCurrentState.isDefault || !mNetworkController.isRadioOn()) {
                 notifyListenersForCarrierWifi(callback);
             }
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
new file mode 100644
index 0000000..d65fa3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.core
+
+import android.app.Fragment
+import com.android.systemui.R
+import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarView
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
+import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import java.lang.IllegalStateException
+import javax.inject.Inject
+
+/**
+ * Responsible for creating the StatusBar window and initializing the root components of that window
+ * (see [CollapsedStatusBarFragment])
+ */
+@StatusBarScope
+class StatusBarInitializer @Inject constructor(
+    private val windowController: StatusBarWindowController
+) {
+
+    var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+
+    /**
+     * Creates the status bar window and root views, and initializes the component
+     */
+    fun initializeStatusBar(
+        sbComponent: StatusBarComponent
+    ) {
+        windowController.fragmentHostManager.addTagListener(
+                CollapsedStatusBarFragment.TAG,
+                object : FragmentHostManager.FragmentListener {
+                    override fun onFragmentViewCreated(tag: String, fragment: Fragment) {
+                        val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment)
+                                .statusBarFragmentComponent ?: throw IllegalStateException()
+                        statusBarViewUpdatedListener?.onStatusBarViewUpdated(
+                            statusBarFragmentComponent.phoneStatusBarView,
+                            statusBarFragmentComponent.phoneStatusBarViewController,
+                            statusBarFragmentComponent.phoneStatusBarTransitions
+                        )
+                    }
+
+                    override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
+                        // nop
+                    }
+                }).fragmentManager
+                .beginTransaction()
+                .replace(R.id.status_bar_container,
+                        sbComponent.createCollapsedStatusBarFragment(),
+                        CollapsedStatusBarFragment.TAG)
+                .commit()
+    }
+
+    interface OnStatusBarViewUpdatedListener {
+        fun onStatusBarViewUpdated(
+            statusBarView: PhoneStatusBarView,
+            statusBarViewController: PhoneStatusBarViewController,
+            statusBarTransitions: PhoneStatusBarTransitions
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 4c7ee17..d574cda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -25,7 +25,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
@@ -169,9 +168,10 @@
     static NotificationListener provideNotificationListener(
             Context context,
             NotificationManager notificationManager,
-            @Main Handler mainHandler) {
+            SystemClock systemClock,
+            @Main Executor mainExecutor) {
         return new NotificationListener(
-                context, notificationManager, mainHandler);
+                context, notificationManager, systemClock, mainExecutor);
     }
 
     /** */
@@ -316,24 +316,15 @@
      */
     @Provides
     @SysUISingleton
-    static LaunchAnimator provideLaunchAnimator(Context context) {
-        return new LaunchAnimator(context);
+    static ActivityLaunchAnimator provideActivityLaunchAnimator() {
+        return new ActivityLaunchAnimator();
     }
 
     /**
      */
     @Provides
     @SysUISingleton
-    static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) {
-        return new ActivityLaunchAnimator(launchAnimator);
-    }
-
-    /**
-     */
-    @Provides
-    @SysUISingleton
-    static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
-            LaunchAnimator launchAnimator, IDreamManager dreamManager) {
-        return new DialogLaunchAnimator(context, launchAnimator, dreamManager);
+    static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) {
+        return new DialogLaunchAnimator(dreamManager);
     }
 }
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 ce86953..962c7fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -102,12 +102,17 @@
 
         configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
             override fun onLayoutDirectionChanged(isRtl: Boolean) {
-                synchronized(this) {
-                    val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
-                    nextViewState = nextViewState.copy(
-                            layoutRtl = isRtl,
-                            designatedCorner = corner
-                    )
+                uiExecutor?.execute {
+                    // If rtl changed, hide all dotes until the next state resolves
+                    setCornerVisibilities(View.INVISIBLE)
+
+                    synchronized(this) {
+                        val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
+                        nextViewState = nextViewState.copy(
+                                layoutRtl = isRtl,
+                                designatedCorner = corner
+                        )
+                    }
                 }
             }
         })
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 a44de2c..a4e2d5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -241,6 +241,10 @@
         configurationController.addCallback(configChangeListener)
         statusBarStateController.addCallback(statusBarStateListener)
 
+        plugin.registerSmartspaceEventNotifier {
+                e -> session?.notifySmartspaceEvent(e)
+        }
+
         reloadSmartspace()
     }
 
@@ -266,6 +270,7 @@
         statusBarStateController.removeCallback(statusBarStateListener)
         session = null
 
+        plugin?.registerSmartspaceEventNotifier(null)
         plugin?.onTargetsAvailable(emptyList())
         Log.d(TAG, "Ending smartspace session for lockscreen")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
index 64a7305..349b191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
@@ -2,6 +2,7 @@
 
 import android.util.MathUtils
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.animation.LaunchAnimator
 import kotlin.math.min
@@ -55,6 +56,7 @@
         }
 
     fun getProgress(delay: Long, duration: Long): Float {
-        return LaunchAnimator.getProgress(linearProgress, delay, duration)
+        return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
+            duration)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 0389a7b..f500d39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
 import com.android.systemui.statusbar.NotificationUiAdjustment;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationRanker;
@@ -107,6 +108,7 @@
     private final LeakDetector mLeakDetector;
     private final ForegroundServiceDismissalFeatureController mFgsFeatureController;
     private final IStatusBarService mStatusBarService;
+    private final NotifLiveDataStoreImpl mNotifLiveDataStore;
     private final DumpManager mDumpManager;
 
     private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
@@ -154,6 +156,7 @@
             LeakDetector leakDetector,
             ForegroundServiceDismissalFeatureController fgsFeatureController,
             IStatusBarService statusBarService,
+            NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager
     ) {
         mLogger = logger;
@@ -164,6 +167,7 @@
         mLeakDetector = leakDetector;
         mFgsFeatureController = fgsFeatureController;
         mStatusBarService = statusBarService;
+        mNotifLiveDataStore = notifLiveDataStore;
         mDumpManager = dumpManager;
     }
 
@@ -725,9 +729,10 @@
             return;
         }
         reapplyFilterAndSort(reason);
-        if (mPresenter != null && !mNotifPipelineFlags.isNewPipelineEnabled()) {
+        if (mPresenter != null) {
             mPresenter.updateNotificationViews(reason);
         }
+        mNotifLiveDataStore.setActiveNotifList(getVisibleNotifications());
     }
 
     public void updateNotificationRanking(RankingMap rankingMap) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index 1940cb2..54f1380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
 
 import javax.inject.Inject;
 
@@ -44,6 +45,7 @@
 @SysUISingleton
 public class NotificationFilter {
 
+    private final DebugModeFilterProvider mDebugNotificationFilter;
     private final StatusBarStateController mStatusBarStateController;
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final ForegroundServiceController mForegroundServiceController;
@@ -52,11 +54,13 @@
 
     @Inject
     public NotificationFilter(
+            DebugModeFilterProvider debugNotificationFilter,
             StatusBarStateController statusBarStateController,
             KeyguardEnvironment keyguardEnvironment,
             ForegroundServiceController foregroundServiceController,
             NotificationLockscreenUserManager userManager,
             MediaFeatureFlag mediaFeatureFlag) {
+        mDebugNotificationFilter = debugNotificationFilter;
         mStatusBarStateController = statusBarStateController;
         mKeyguardEnvironment = keyguardEnvironment;
         mForegroundServiceController = foregroundServiceController;
@@ -69,6 +73,10 @@
      */
     public boolean shouldFilterOut(NotificationEntry entry) {
         final StatusBarNotification sbn = entry.getSbn();
+        if (mDebugNotificationFilter.shouldFilterOut(entry)) {
+            return true;
+        }
+
         if (!(mKeyguardEnvironment.isDeviceProvisioned()
                 || showNotificationEvenIfUnprovisioned(sbn))) {
             return true;
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 38b5ee8..74aedb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -27,7 +27,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionListener
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
@@ -40,7 +40,7 @@
     private val statusBarStateController: StatusBarStateController,
     private val bypassController: KeyguardBypassController,
     private val dozeParameters: DozeParameters,
-    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController
+    private val screenOffAnimationController: ScreenOffAnimationController
 ) : OnHeadsUpChangedListener, StatusBarStateController.StateListener, PanelExpansionListener {
 
     private val mNotificationVisibility = object : FloatProperty<NotificationWakeUpCoordinator>(
@@ -264,15 +264,13 @@
     }
 
     override fun onStateChanged(newState: Int) {
-        if (dozeParameters.shouldControlUnlockedScreenOff()) {
-            if (unlockedScreenOffAnimationController.isScreenOffAnimationPlaying() &&
-                    state == StatusBarState.KEYGUARD &&
-                    newState == StatusBarState.SHADE) {
-                // If we're animating the screen off and going from KEYGUARD back to SHADE, the
-                // animation was cancelled and we are unlocking. Override the doze amount to 0f (not
-                // dozing) so that the notifications are no longer hidden.
-                setDozeAmount(0f, 0f)
-            }
+        if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard() &&
+            state == StatusBarState.KEYGUARD &&
+            newState == StatusBarState.SHADE) {
+            // If we're animating the screen off and going from KEYGUARD back to SHADE, the
+            // animation was cancelled and we are unlocking. Override the doze amount to 0f (not
+            // dozing) so that the notifications are no longer hidden.
+            setDozeAmount(0f, 0f)
         }
 
         if (overrideDozeAmountIfAnimatingScreenOff(mLinearDozeAmount)) {
@@ -332,7 +330,7 @@
      * animation. If true, the original doze amount should be ignored.
      */
     private fun overrideDozeAmountIfAnimatingScreenOff(linearDozeAmount: Float): Boolean {
-        if (unlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+        if (screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()) {
             setDozeAmount(1f, 1f)
             return true
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt
new file mode 100644
index 0000000..ef00627
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStore.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.notification.collection
+
+import androidx.lifecycle.Observer
+
+/**
+ * An object which provides pieces of information about the notification shade.
+ *
+ * Note that individual fields of this object are updated together before synchronous observers are
+ * notified, so synchronous observers of two fields can be assured that they will see consistent
+ * results: e.g. if [hasActiveNotifs] is false then [activeNotifList] will be empty, and vice versa.
+ *
+ * This interface is read-only.
+ */
+interface NotifLiveDataStore {
+    val hasActiveNotifs: NotifLiveData<Boolean>
+    val activeNotifCount: NotifLiveData<Int>
+    val activeNotifList: NotifLiveData<List<NotificationEntry>>
+}
+
+/**
+ * An individual value which can be accessed directly, or observed for changes either synchronously
+ * or asynchronously.
+ *
+ * This interface is read-only.
+ */
+interface NotifLiveData<T> {
+    /** Access the current value */
+    val value: T
+    /** Add an observer which will be invoked synchronously when the value is changed. */
+    fun addSyncObserver(observer: Observer<T>)
+    /** Add an observer which will be invoked asynchronously after the value has changed */
+    fun addAsyncObserver(observer: Observer<T>)
+    /** Remove an observer previously added with [addSyncObserver] or [addAsyncObserver]. */
+    fun removeObserver(observer: Observer<T>)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
new file mode 100644
index 0000000..8aa6b81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.notification.collection
+
+import androidx.lifecycle.Observer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.Assert
+import com.android.systemui.util.ListenerSet
+import com.android.systemui.util.isNotEmpty
+import com.android.systemui.util.traceSection
+import java.util.Collections.unmodifiableList
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicReference
+import javax.inject.Inject
+
+/** Writeable implementation of [NotifLiveDataStore] */
+@SysUISingleton
+class NotifLiveDataStoreImpl @Inject constructor(
+    @Main private val mainExecutor: Executor
+) : NotifLiveDataStore {
+    private val hasActiveNotifsPrivate = NotifLiveDataImpl(
+        name = "hasActiveNotifs",
+        initialValue = false,
+        mainExecutor
+    )
+    private val activeNotifCountPrivate = NotifLiveDataImpl(
+        name = "activeNotifCount",
+        initialValue = 0,
+        mainExecutor
+    )
+    private val activeNotifListPrivate = NotifLiveDataImpl(
+        name = "activeNotifList",
+        initialValue = listOf<NotificationEntry>(),
+        mainExecutor
+    )
+
+    override val hasActiveNotifs: NotifLiveData<Boolean> = hasActiveNotifsPrivate
+    override val activeNotifCount: NotifLiveData<Int> = activeNotifCountPrivate
+    override val activeNotifList: NotifLiveData<List<NotificationEntry>> = activeNotifListPrivate
+
+    /** Set the latest flattened list of notification entries. */
+    fun setActiveNotifList(flatEntryList: List<NotificationEntry>) {
+        traceSection("NotifLiveDataStore.setActiveNotifList") {
+            Assert.isMainThread()
+            val unmodifiableCopy = unmodifiableList(flatEntryList.toList())
+            // This ensures we set all values before dispatching to any observers
+            listOf(
+                activeNotifListPrivate.setValueAndProvideDispatcher(unmodifiableCopy),
+                activeNotifCountPrivate.setValueAndProvideDispatcher(unmodifiableCopy.size),
+                hasActiveNotifsPrivate.setValueAndProvideDispatcher(unmodifiableCopy.isNotEmpty())
+            ).forEach { dispatcher -> dispatcher.invoke() }
+        }
+    }
+}
+
+/** Read-write implementation of [NotifLiveData] */
+class NotifLiveDataImpl<T>(
+    private val name: String,
+    initialValue: T,
+    @Main private val mainExecutor: Executor
+) : NotifLiveData<T> {
+    private val syncObservers = ListenerSet<Observer<T>>()
+    private val asyncObservers = ListenerSet<Observer<T>>()
+    private val atomicValue = AtomicReference(initialValue)
+    private var lastAsyncValue: T? = null
+
+    private fun dispatchToAsyncObservers() {
+        val value = atomicValue.get()
+        if (lastAsyncValue != value) {
+            lastAsyncValue = value
+            traceSection("NotifLiveData($name).dispatchToAsyncObservers") {
+                asyncObservers.forEach { it.onChanged(value) }
+            }
+        }
+    }
+
+    /**
+     * Access or set the current value.
+     *
+     * When setting, sync observers will be dispatched synchronously, and a task will be posted to
+     * dispatch the value to async observers.
+     */
+    override var value: T
+        get() = atomicValue.get()
+        set(value) = setValueAndProvideDispatcher(value).invoke()
+
+    /**
+     * Set the value, and return a function that when invoked will dispatch to the observers.
+     *
+     * This is intended to allow multiple instances with related data to be updated together and
+     * have their dispatchers invoked after all data has been updated.
+     */
+    fun setValueAndProvideDispatcher(value: T): () -> Unit {
+        val oldValue = atomicValue.getAndSet(value)
+        if (oldValue != value) {
+            return {
+                if (syncObservers.isNotEmpty()) {
+                    traceSection("NotifLiveData($name).dispatchToSyncObservers") {
+                        syncObservers.forEach { it.onChanged(value) }
+                    }
+                }
+                if (asyncObservers.isNotEmpty()) {
+                    mainExecutor.execute(::dispatchToAsyncObservers)
+                }
+            }
+        }
+        return {}
+    }
+
+    override fun addSyncObserver(observer: Observer<T>) {
+        syncObservers.addIfAbsent(observer)
+    }
+
+    override fun addAsyncObserver(observer: Observer<T>) {
+        asyncObservers.addIfAbsent(observer)
+    }
+
+    override fun removeObserver(observer: Observer<T>) {
+        syncObservers.remove(observer)
+        asyncObservers.remove(observer)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index 6fbed9a8..5ada7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -245,36 +245,4 @@
     fun getInternalNotifUpdater(name: String?): InternalNotifUpdater {
         return mNotifCollection.getInternalNotifUpdater(name)
     }
-
-    /**
-     * Returns a read-only view in to the current shade list, i.e. the list of notifications that
-     * are currently present in the shade.
-     * @throws IllegalStateException if called during pipeline execution.
-     */
-    val shadeList: List<ListEntry>
-        get() = mShadeListBuilder.shadeList
-
-    /**
-     * Constructs a flattened representation of the notification tree, where each group will have
-     * the summary (if present) followed by the children.
-     * @throws IllegalStateException if called during pipeline execution.
-     */
-    fun getFlatShadeList(): List<NotificationEntry> = shadeList.flatMap { entry ->
-        when (entry) {
-            is NotificationEntry -> sequenceOf(entry)
-            is GroupEntry -> sequenceOf(entry.summary).filterNotNull() + entry.children
-            else -> throw RuntimeException("Unexpected entry $entry")
-        }
-    }
-
-    /**
-     * Returns the number of notifications currently shown in the shade. This includes all
-     * children and summary notifications.
-     * @throws IllegalStateException if called during pipeline execution.
-     */
-    fun getShadeListCount(): Int = shadeList.sumOf { entry ->
-        // include the summary in the count
-        if (entry is GroupEntry) 1 + entry.children.size
-        else 1
-    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
new file mode 100644
index 0000000..8e307ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.notification.collection.coordinator
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl
+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.render.requireSummary
+import javax.inject.Inject
+
+/**
+ * A small coordinator which updates the notif stack (the view layer which holds notifications)
+ * with high-level data after the stack is populated with the final entries.
+ */
+@CoordinatorScope
+class DataStoreCoordinator @Inject internal constructor(
+    private val notifLiveDataStoreImpl: NotifLiveDataStoreImpl
+) : Coordinator {
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addOnAfterRenderListListener { entries, _ -> onAfterRenderList(entries) }
+    }
+
+    fun onAfterRenderList(entries: List<ListEntry>) {
+        val flatEntryList = flattenedEntryList(entries)
+        notifLiveDataStoreImpl.setActiveNotifList(flatEntryList)
+    }
+
+    private fun flattenedEntryList(entries: List<ListEntry>) =
+        mutableListOf<NotificationEntry>().also { list ->
+            entries.forEach { entry ->
+                when (entry) {
+                    is NotificationEntry -> list.add(entry)
+                    is GroupEntry -> {
+                        list.add(entry.requireSummary)
+                        list.addAll(entry.children)
+                    }
+                    else -> error("Unexpected entry $entry")
+                }
+            }
+        }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
new file mode 100644
index 0000000..df54ccd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.notification.collection.coordinator
+
+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.DebugModeFilterProvider
+import javax.inject.Inject
+
+/** A small coordinator which filters out notifications from non-allowed apps. */
+@CoordinatorScope
+class DebugModeCoordinator @Inject constructor(
+    private val debugModeFilterProvider: DebugModeFilterProvider
+) : Coordinator {
+
+    override fun attach(pipeline: NotifPipeline) {
+        pipeline.addPreGroupFilter(preGroupFilter)
+        debugModeFilterProvider.registerInvalidationListener(preGroupFilter::invalidateList)
+    }
+
+    private val preGroupFilter = object : NotifFilter("DebugModeCoordinator") {
+        override fun shouldFilterOut(entry: NotificationEntry, now: Long) =
+            debugModeFilterProvider.shouldFilterOut(entry)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index a16b565..757fb5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -35,6 +35,7 @@
 class NotifCoordinatorsImpl @Inject constructor(
     dumpManager: DumpManager,
     notifPipelineFlags: NotifPipelineFlags,
+    dataStoreCoordinator: DataStoreCoordinator,
     hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
     hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
     keyguardCoordinator: KeyguardCoordinator,
@@ -44,10 +45,12 @@
     bubbleCoordinator: BubbleCoordinator,
     headsUpCoordinator: HeadsUpCoordinator,
     gutsCoordinator: GutsCoordinator,
+    communalCoordinator: CommunalCoordinator,
     conversationCoordinator: ConversationCoordinator,
-    preparationCoordinator: PreparationCoordinator,
+    debugModeCoordinator: DebugModeCoordinator,
     groupCountCoordinator: GroupCountCoordinator,
     mediaCoordinator: MediaCoordinator,
+    preparationCoordinator: PreparationCoordinator,
     remoteInputCoordinator: RemoteInputCoordinator,
     rowAppearanceCoordinator: RowAppearanceCoordinator,
     stackCoordinator: StackCoordinator,
@@ -55,7 +58,6 @@
     smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
     viewConfigCoordinator: ViewConfigCoordinator,
     visualStabilityCoordinator: VisualStabilityCoordinator,
-    communalCoordinator: CommunalCoordinator,
     sensitiveContentCoordinator: SensitiveContentCoordinator
 ) : NotifCoordinators {
 
@@ -67,6 +69,16 @@
      */
     init {
         dumpManager.registerDumpable(TAG, this)
+
+        // TODO(b/208866714): formalize the system by which some coordinators may be required by the
+        //  pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical
+        //  glue between the pipeline and parts of SystemUI which depend on pipeline output via the
+        //  NotifLiveDataStore.
+        if (notifPipelineFlags.isNewPipelineEnabled()) {
+            mCoordinators.add(dataStoreCoordinator)
+        }
+
+        // Attach normal coordinators.
         mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
@@ -74,16 +86,16 @@
         mCoordinators.add(appOpsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
         mCoordinators.add(bubbleCoordinator)
+        mCoordinators.add(communalCoordinator)
+        mCoordinators.add(debugModeCoordinator)
         mCoordinators.add(conversationCoordinator)
         mCoordinators.add(groupCountCoordinator)
         mCoordinators.add(mediaCoordinator)
-        mCoordinators.add(remoteInputCoordinator)
         mCoordinators.add(rowAppearanceCoordinator)
         mCoordinators.add(stackCoordinator)
         mCoordinators.add(shadeEventCoordinator)
         mCoordinators.add(viewConfigCoordinator)
         mCoordinators.add(visualStabilityCoordinator)
-        mCoordinators.add(communalCoordinator)
         mCoordinators.add(sensitiveContentCoordinator)
         if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
             mCoordinators.add(smartspaceDedupingCoordinator)
@@ -92,6 +104,7 @@
             mCoordinators.add(headsUpCoordinator)
             mCoordinators.add(gutsCoordinator)
             mCoordinators.add(preparationCoordinator)
+            mCoordinators.add(remoteInputCoordinator)
         }
 
         // Manually add Ordered Sections
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 38f11fc..c6a8a69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -19,8 +19,8 @@
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
-import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
+import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import javax.inject.Inject
@@ -49,10 +49,12 @@
         var hasNonClearableSilentNotifs = false
         var hasClearableSilentNotifs = false
         entries.forEach {
-            val isSilent = it.section!!.bucket == BUCKET_SILENT
+            val section = checkNotNull(it.section) { "Null section for ${it.key}" }
+            val entry = checkNotNull(it.representativeEntry) { "Null notif entry for ${it.key}" }
+            val isSilent = section.bucket == BUCKET_SILENT
             // NOTE: NotificationEntry.isClearable will internally check group children to ensure
             //  the group itself definitively clearable.
-            val isClearable = it.representativeEntry!!.isClearable
+            val isClearable = entry.isClearable
             when {
                 isSilent && isClearable -> hasClearableSilentNotifs = true
                 isSilent && !isClearable -> hasNonClearableSilentNotifs = true
@@ -60,13 +62,12 @@
                 !isSilent && !isClearable -> hasNonClearableAlertingNotifs = true
             }
         }
-        val stats = NotifStats(
+        return NotifStats(
             numActiveNotifs = entries.size,
             hasNonClearableAlertingNotifs = hasNonClearableAlertingNotifs,
             hasClearableAlertingNotifs = hasClearableAlertingNotifs,
             hasNonClearableSilentNotifs = hasNonClearableSilentNotifs,
             hasClearableSilentNotifs = hasClearableSilentNotifs
         )
-        return stats
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt
deleted file mode 100644
index 5c70f32..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationVisibilityProvider.kt
+++ /dev/null
@@ -1,50 +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.notification.collection.legacy
-
-import com.android.internal.statusbar.NotificationVisibility
-import com.android.systemui.statusbar.notification.NotificationEntryManager
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.logging.NotificationLogger
-import javax.inject.Inject
-
-/** Legacy pipeline implementation for getting [NotificationVisibility]. */
-class LegacyNotificationVisibilityProvider @Inject constructor(
-    private val notifEntryManager: NotificationEntryManager
-) : NotificationVisibilityProvider {
-    override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility {
-        val count: Int = notifEntryManager.activeNotificationsCount
-        val rank = entry.ranking.rank
-        val hasRow = entry.row != null
-        val location = NotificationLogger.getNotificationLocation(entry)
-        return NotificationVisibility.obtain(entry.key, rank, count, visible && hasRow, location)
-    }
-
-    override fun obtain(key: String, visible: Boolean): NotificationVisibility {
-        val entry: NotificationEntry? = notifEntryManager.getActiveNotificationUnfiltered(key)
-        val count: Int = notifEntryManager.activeNotificationsCount
-        val rank = entry?.ranking?.rank ?: -1
-        val hasRow = entry?.row != null
-        val location = NotificationLogger.getNotificationLocation(entry)
-        return NotificationVisibility.obtain(key, rank, count, visible && hasRow, location)
-    }
-
-    override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
-            NotificationLogger.getNotificationLocation(
-                    notifEntryManager.getActiveNotificationUnfiltered(key))
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
new file mode 100644
index 0000000..d16d76a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.notification.collection.provider
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Build
+import android.util.Log
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.Assert
+import com.android.systemui.util.ListenerSet
+import com.android.systemui.util.isNotEmpty
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * A debug mode provider which is used by both the legacy and new notification pipelines to
+ * block unwanted notifications from appearing to the user, primarily for integration testing.
+ *
+ * The only configuration is a list of allowed packages.  When this list is empty, the feature is
+ * disabled.  When SystemUI starts up, this feature is disabled.
+ *
+ * To enabled filtering, provide the list of packages in a comma-separated list using the command:
+ *
+ * `$ adb shell am broadcast -a com.android.systemui.action.SET_NOTIF_DEBUG_MODE
+ *          --esal allowed_packages <comma-separated-packages>`
+ *
+ * To disable filtering, send the action without a list:
+ *
+ * `$ adb shell am broadcast -a com.android.systemui.action.SET_NOTIF_DEBUG_MODE`
+ *
+ * NOTE: this feature only works on debug builds, and when the broadcaster is root.
+ */
+@SysUISingleton
+class DebugModeFilterProvider @Inject constructor(
+    private val context: Context,
+    dumpManager: DumpManager
+) : Dumpable {
+    private var allowedPackages: List<String> = emptyList()
+    private val listeners = ListenerSet<Runnable>()
+
+    init {
+        dumpManager.registerDumpable(this)
+    }
+
+    /**
+     * Register a runnable to be invoked when the allowed packages changes, which would mean the
+     * result of [shouldFilterOut] may have changed for some entries.
+     */
+    fun registerInvalidationListener(listener: Runnable) {
+        Assert.isMainThread()
+        if (!Build.isDebuggable()) {
+            return
+        }
+        val needsInitialization = listeners.isEmpty()
+        listeners.addIfAbsent(listener)
+        if (needsInitialization) {
+            val filter = IntentFilter().apply { addAction(ACTION_SET_NOTIF_DEBUG_MODE) }
+            val permission = NOTIF_DEBUG_MODE_PERMISSION
+            context.registerReceiver(mReceiver, filter, permission, null)
+            Log.d(TAG, "Registered: $mReceiver")
+        }
+    }
+
+    /**
+     * Determine if the given entry should be hidden from the user in debug mode.
+     * Will always return false in release.
+     */
+    fun shouldFilterOut(entry: NotificationEntry): Boolean {
+        if (allowedPackages.isEmpty()) {
+            return false
+        }
+        return entry.sbn.packageName !in allowedPackages
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("initialized: ${listeners.isNotEmpty()}")
+        pw.println("allowedPackages: ${allowedPackages.size}")
+        allowedPackages.forEachIndexed { i, pkg ->
+            pw.println("  [$i]: $pkg")
+        }
+    }
+
+    private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent?) {
+            val action = intent?.action
+            if (ACTION_SET_NOTIF_DEBUG_MODE == action) {
+                allowedPackages = intent.extras?.getStringArrayList(EXTRA_ALLOWED_PACKAGES)
+                    ?: emptyList()
+                Log.d(TAG, "Updated allowedPackages: $allowedPackages")
+                listeners.forEach(Runnable::run)
+            } else {
+                Log.d(TAG, "Malformed intent: $intent")
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "DebugModeFilterProvider"
+        private const val ACTION_SET_NOTIF_DEBUG_MODE =
+            "com.android.systemui.action.SET_NOTIF_DEBUG_MODE"
+        private const val NOTIF_DEBUG_MODE_PERMISSION =
+            "com.android.systemui.permission.NOTIF_DEBUG_MODE"
+        private const val EXTRA_ALLOWED_PACKAGES = "allowed_packages"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
similarity index 65%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
index 51de08d..6a1e36f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
@@ -14,20 +14,24 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.notification.collection.render
+package com.android.systemui.statusbar.notification.collection.provider
 
 import com.android.internal.statusbar.NotificationVisibility
-import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
 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
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import javax.inject.Inject
 
-/** New pipeline implementation for getting [NotificationVisibility]. */
+/** pipeline-agnostic implementation for getting [NotificationVisibility]. */
 class NotificationVisibilityProviderImpl @Inject constructor(
-    private val notifPipeline: NotifPipeline
+    private val notifDataStore: NotifLiveDataStore,
+    private val notifCollection: CommonNotifCollection
 ) : NotificationVisibilityProvider {
+
     override fun obtain(entry: NotificationEntry, visible: Boolean): NotificationVisibility {
-        val count: Int = notifPipeline.getShadeListCount()
+        val count: Int = getCount()
         val rank = entry.ranking.rank
         val hasRow = entry.row != null
         val location = NotificationLogger.getNotificationLocation(entry)
@@ -35,9 +39,11 @@
     }
 
     override fun obtain(key: String, visible: Boolean): NotificationVisibility =
-        notifPipeline.getEntry(key)?.let { return obtain(it, visible) }
-            ?: NotificationVisibility.obtain(key, -1, notifPipeline.getShadeListCount(), false)
+        notifCollection.getEntry(key)?.let { return obtain(it, visible) }
+            ?: NotificationVisibility.obtain(key, -1, getCount(), false)
 
     override fun getLocation(key: String): NotificationVisibility.NotificationLocation =
-            NotificationLogger.getNotificationLocation(notifPipeline.getEntry(key))
+        NotificationLogger.getNotificationLocation(notifCollection.getEntry(key))
+
+    private fun getCount() = notifDataStore.activeNotifCount.value
 }
\ No newline at end of file
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 d25a2d3..f1cba34 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
@@ -44,6 +44,8 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
 import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
@@ -52,12 +54,12 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
 import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
 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.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -65,7 +67,6 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProviderImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -122,6 +123,7 @@
             LeakDetector leakDetector,
             ForegroundServiceDismissalFeatureController fgsFeatureController,
             IStatusBarService statusBarService,
+            NotifLiveDataStoreImpl notifLiveDataStore,
             DumpManager dumpManager) {
         return new NotificationEntryManager(
                 logger,
@@ -132,6 +134,7 @@
                 leakDetector,
                 fgsFeatureController,
                 statusBarService,
+                notifLiveDataStore,
                 dumpManager);
     }
 
@@ -212,6 +215,7 @@
             NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
             NotifPipelineFlags notifPipelineFlags,
+            NotifLiveDataStore notifLiveDataStore,
             NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -222,6 +226,7 @@
                 notificationListener,
                 uiBgExecutor,
                 notifPipelineFlags,
+                notifLiveDataStore,
                 visibilityProvider,
                 entryManager,
                 notifPipeline,
@@ -290,16 +295,10 @@
     /**
      * Provide the object which can be used to obtain NotificationVisibility objects.
      */
-    @Provides
+    @Binds
     @SysUISingleton
-    static NotificationVisibilityProvider provideNotificationVisibilityProvider(
-            NotifPipelineFlags notifPipelineFlags,
-            Lazy<NotificationVisibilityProviderImpl> newProvider,
-            Lazy<LegacyNotificationVisibilityProvider> legacyProvider) {
-        return notifPipelineFlags.isNewPipelineEnabled()
-                ? newProvider.get()
-                : legacyProvider.get();
-    }
+    NotificationVisibilityProvider provideNotificationVisibilityProvider(
+            NotificationVisibilityProviderImpl newProvider);
 
     /**
      * Provide the active implementation for presenting notifications.
@@ -356,4 +355,8 @@
     /** */
     @Binds
     NotifInflater bindNotifInflater(NotifInflaterImpl notifInflaterImpl);
+
+    /** */
+    @Binds
+    NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 212c342e..38f3c39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -28,12 +28,15 @@
 import com.android.systemui.statusbar.notification.NotificationClicker
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.NotificationListController
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
 import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider
 import com.android.systemui.statusbar.notification.collection.render.NotifStackController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -63,10 +66,13 @@
     private val notifPipelineFlags: NotifPipelineFlags,
     private val notificationListener: NotificationListener,
     private val entryManager: NotificationEntryManager,
+    private val debugModeFilterProvider: DebugModeFilterProvider,
     private val legacyRanker: NotificationRankingManager,
+    private val commonNotifCollection: Lazy<CommonNotifCollection>,
     private val notifPipeline: Lazy<NotifPipeline>,
+    private val notifLiveDataStore: NotifLiveDataStore,
     private val targetSdkResolver: TargetSdkResolver,
-    private val newNotifPipeline: Lazy<NotifPipelineInitializer>,
+    private val newNotifPipelineInitializer: Lazy<NotifPipelineInitializer>,
     private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
@@ -111,7 +117,7 @@
         animatedImageNotificationManager.bind()
 
         if (INITIALIZE_NEW_PIPELINE) {
-            newNotifPipeline.get().initialize(
+            newNotifPipelineInitializer.get().initialize(
                     notificationListener,
                     notificationRowBinder,
                     listContainer,
@@ -130,6 +136,9 @@
             headsUpController.attach(entryManager, headsUpManager)
             groupManagerLegacy.get().setHeadsUpManager(headsUpManager)
             groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
+            debugModeFilterProvider.registerInvalidationListener {
+                entryManager.updateNotifications("debug mode filter changed")
+            }
 
             entryManager.initialize(notificationListener, legacyRanker)
         }
@@ -155,14 +164,10 @@
     }
 
     override fun resetUserExpandedStates() {
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            for (entry in notifPipeline.get().allNotifs) {
-                entry.resetUserExpansion()
-            }
-        } else {
-            for (entry in entryManager.visibleNotifications) {
-                entry.resetUserExpansion()
-            }
+        // TODO: this is a view thing that should be done through the views, but that means doing it
+        //  both when this event is fired and any time a row is attached.
+        for (entry in commonNotifCollection.get().allNotifs) {
+            entry.resetUserExpansion()
         }
     }
 
@@ -177,11 +182,7 @@
     }
 
     override fun getActiveNotificationsCount(): Int =
-        if (notifPipelineFlags.isNewPipelineEnabled()) {
-            notifPipeline.get().getShadeListCount()
-        } else {
-            entryManager.activeNotificationsCount
-        }
+        notifLiveDataStore.activeNotifCount.value
 
     companion object {
         // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
index f8d6c6d..b2e15f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/BypassHeadsUpNotifier.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.NotificationMediaManager
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.tuner.TunerService
@@ -44,7 +44,7 @@
     private val headsUpManager: HeadsUpManagerPhone,
     private val notificationLockscreenUserManager: NotificationLockscreenUserManager,
     private val mediaManager: NotificationMediaManager,
-    private val entryManager: NotificationEntryManager,
+    private val commonNotifCollection: CommonNotifCollection,
     tunerService: TunerService
 ) : StatusBarStateController.StateListener, NotificationMediaManager.MediaListener {
 
@@ -77,8 +77,7 @@
 
     override fun onPrimaryMetadataOrStateChanged(metadata: MediaMetadata?, state: Int) {
         val previous = currentMediaEntry
-        var newEntry = entryManager
-                .getActiveNotificationUnfiltered(mediaManager.mediaNotificationKey)
+        var newEntry = commonNotifCollection.getEntry(mediaManager.mediaNotificationKey)
         if (!NotificationMediaManager.isPlayingState(state)) {
             newEntry = null
         }
@@ -112,7 +111,7 @@
             // filter notifications invisible on Keyguard
             return false
         }
-        if (entryManager.getActiveNotificationUnfiltered(entry.key) != null) {
+        if (commonNotifCollection.getEntry(entry.key) != null) {
             // filter notifications not the active list currently
             return false
         }
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 bd1f609..9e8200b 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
@@ -20,6 +20,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
@@ -41,6 +42,7 @@
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 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;
@@ -75,7 +77,7 @@
     // Dependencies:
     private final NotificationListenerService mNotificationListener;
     private final Executor mUiBgExecutor;
-    private final NotifPipelineFlags mNotifPipelineFlags;
+    private final NotifLiveDataStore mNotifLiveDataStore;
     private final NotificationVisibilityProvider mVisibilityProvider;
     private final NotificationEntryManager mEntryManager;
     private final NotifPipeline mNotifPipeline;
@@ -166,6 +168,9 @@
 
             mExpansionStateLogger.onVisibilityChanged(
                     mTmpCurrentlyVisibleNotifications, mTmpCurrentlyVisibleNotifications);
+            Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Active]", N);
+            Trace.traceCounter(Trace.TRACE_TAG_APP, "Notifications [Visible]",
+                    mCurrentlyVisibleNotifications.size());
 
             recycleAllVisibilityObjects(mTmpNoLongerVisibleNotifications);
             mTmpCurrentlyVisibleNotifications.clear();
@@ -175,11 +180,7 @@
     };
 
     private List<NotificationEntry> getVisibleNotifications() {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            return mNotifPipeline.getFlatShadeList();
-        } else {
-            return mEntryManager.getVisibleNotifications();
-        }
+        return mNotifLiveDataStore.getActiveNotifList().getValue();
     }
 
     /**
@@ -219,6 +220,7 @@
     public NotificationLogger(NotificationListener notificationListener,
             @UiBackground Executor uiBgExecutor,
             NotifPipelineFlags notifPipelineFlags,
+            NotifLiveDataStore notifLiveDataStore,
             NotificationVisibilityProvider visibilityProvider,
             NotificationEntryManager entryManager,
             NotifPipeline notifPipeline,
@@ -227,7 +229,7 @@
             NotificationPanelLogger notificationPanelLogger) {
         mNotificationListener = notificationListener;
         mUiBgExecutor = uiBgExecutor;
-        mNotifPipelineFlags = notifPipelineFlags;
+        mNotifLiveDataStore = notifLiveDataStore;
         mVisibilityProvider = visibilityProvider;
         mEntryManager = entryManager;
         mNotifPipeline = notifPipeline;
@@ -238,7 +240,7 @@
         // Not expected to be destroyed, don't need to unsubscribe
         statusBarStateController.addCallback(this);
 
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
+        if (notifPipelineFlags.isNewPipelineEnabled()) {
             registerNewPipelineListener();
         } else {
             registerLegacyListener();
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 2eb2065..63cb4ae 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
@@ -579,14 +579,24 @@
         if (contentView.hasOverlappingRendering()) {
             int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
                     : LAYER_TYPE_HARDWARE;
-            int currentLayerType = contentView.getLayerType();
-            if (currentLayerType != layerType) {
-                contentView.setLayerType(layerType, null);
-            }
+            contentView.setLayerType(layerType, null);
         }
         contentView.setAlpha(contentAlpha);
+        // After updating the current view, reset all views.
+        if (contentAlpha == 1f) {
+            resetAllContentAlphas();
+        }
     }
 
+    /**
+     * If a subclass's {@link #getContentView()} returns different views depending on state,
+     * this method is an opportunity to reset the alpha of ALL content views, not just the
+     * current one, which may prevent a content view that is temporarily hidden from being reset.
+     *
+     * This should setAlpha(1.0f) and setLayerType(LAYER_TYPE_NONE) for all content views.
+     */
+    protected void resetAllContentAlphas() {}
+
     @Override
     protected void applyRoundness() {
         super.applyRoundness();
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 82ed34c..f898470 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
@@ -2160,15 +2160,6 @@
     }
 
     public void setExpandAnimationRunning(boolean expandAnimationRunning) {
-        View contentView;
-        if (mIsSummaryWithChildren) {
-            contentView =  mChildrenContainer;
-        } else {
-            contentView = getShowingLayout();
-        }
-        if (mGuts != null && mGuts.isExposed()) {
-            contentView = mGuts;
-        }
         if (expandAnimationRunning) {
             setAboveShelf(true);
             mExpandAnimationRunning = true;
@@ -2181,9 +2172,7 @@
             if (mGuts != null) {
                 mGuts.setAlpha(1.0f);
             }
-            if (contentView != null) {
-                contentView.setAlpha(1.0f);
-            }
+            resetAllContentAlphas();
             setExtraWidthForClipping(0.0f);
             if (mNotificationParent != null) {
                 mNotificationParent.setExtraWidthForClipping(0.0f);
@@ -2632,10 +2621,8 @@
             mPrivateLayout.animate().cancel();
             if (mChildrenContainer != null) {
                 mChildrenContainer.animate().cancel();
-                mChildrenContainer.setAlpha(1f);
             }
-            mPublicLayout.setAlpha(1f);
-            mPrivateLayout.setAlpha(1f);
+            resetAllContentAlphas();
             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
             updateChildrenVisibility();
         } else {
@@ -2662,7 +2649,10 @@
                     .alpha(0f)
                     .setStartDelay(delay)
                     .setDuration(duration)
-                    .withEndAction(() -> hiddenView.setVisibility(View.INVISIBLE));
+                    .withEndAction(() -> {
+                        hiddenView.setVisibility(View.INVISIBLE);
+                        resetAllContentAlphas();
+                    });
         }
         for (View showView : shownChildren) {
             showView.setVisibility(View.VISIBLE);
@@ -2785,12 +2775,7 @@
         if (wasAppearing) {
             // During the animation the visible view might have changed, so let's make sure all
             // alphas are reset
-            if (mChildrenContainer != null) {
-                mChildrenContainer.setAlpha(1.0f);
-            }
-            for (NotificationContentView l : mLayouts) {
-                l.setAlpha(1.0f);
-            }
+            resetAllContentAlphas();
             if (FADE_LAYER_OPTIMIZATION_ENABLED) {
                 setNotificationFaded(false);
             } else {
@@ -2801,6 +2786,18 @@
         }
     }
 
+    @Override
+    protected void resetAllContentAlphas() {
+        mPrivateLayout.setAlpha(1f);
+        mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null);
+        mPublicLayout.setAlpha(1f);
+        mPublicLayout.setLayerType(LAYER_TYPE_NONE, null);
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setAlpha(1f);
+            mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
+        }
+    }
+
     /** Gets the last value set with {@link #setNotificationFaded(boolean)} */
     @Override
     public boolean isNotificationFaded() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index bfe352d..aa511c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -146,6 +146,7 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
+        dispatchConfigurationChanged(getResources().getConfiguration());
     }
 
     @Override
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 a9cc3237..e658468 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
@@ -59,6 +59,7 @@
     private float mMaxHeadsUpTranslation;
     private boolean mDismissAllInProgress;
     private int mLayoutMinHeight;
+    private int mLayoutMaxHeight;
     private NotificationShelf mShelf;
     private int mZDistanceBetweenElements;
     private int mBaseZHeight;
@@ -326,6 +327,14 @@
         mLayoutHeight = layoutHeight;
     }
 
+    public void setLayoutMaxHeight(int maxLayoutHeight) {
+        mLayoutMaxHeight = maxLayoutHeight;
+    }
+
+    public int getLayoutMaxHeight() {
+        return mLayoutMaxHeight;
+    }
+
     public float getTopPadding() {
         return mTopPadding;
     }
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 44e3718..79b05c9 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
@@ -104,9 +104,9 @@
 import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
 import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.HeadsUpUtil;
 import com.android.systemui.statusbar.policy.ScrollAdapter;
 import com.android.systemui.util.Assert;
@@ -120,6 +120,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -411,7 +412,7 @@
     private NotificationShelf mShelf;
     private int mMaxDisplayedNotifications = -1;
     private float mKeyguardBottomPadding = -1;
-    private int mStatusBarHeight;
+    @VisibleForTesting int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
     private boolean mIsClipped;
@@ -528,7 +529,7 @@
     private NotificationEntry mTopHeadsUpEntry;
     private long mNumHeadsUp;
     private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
     private boolean mShouldUseSplitNotificationShade;
 
     private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
@@ -569,8 +570,8 @@
         super(context, attrs, 0, 0);
         Resources res = getResources();
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
-        mUnlockedScreenOffAnimationController =
-                Dependency.get(UnlockedScreenOffAnimationController.class);
+        mScreenOffAnimationController =
+                Dependency.get(ScreenOffAnimationController.class);
         updateSplitNotificationShade();
         mSectionsManager.initialize(this, LayoutInflater.from(context));
         mSections = mSectionsManager.createSectionsForBuckets();
@@ -668,6 +669,7 @@
 
     public void setIsRemoteInputActive(boolean isActive) {
         mIsRemoteInputActive = isActive;
+        updateFooter();
     }
 
     @VisibleForTesting
@@ -682,7 +684,8 @@
         boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
                 && mIsCurrentUserSetup  // see: b/193149550
                 && mStatusBarState != StatusBarState.KEYGUARD
-                && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                && mQsExpansionFraction != 1
+                && !mScreenOffAnimationController.shouldHideNotificationsFooter()
                 && !mIsRemoteInputActive;
         boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
@@ -727,38 +730,59 @@
         }
 
         if (DEBUG) {
-            int y = mTopPadding;
-            mDebugPaint.setColor(Color.RED);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = getLayoutHeight();
-            mDebugPaint.setColor(Color.YELLOW);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = (int) mMaxLayoutHeight;
-            mDebugPaint.setColor(Color.MAGENTA);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            if (mKeyguardBottomPadding >= 0) {
-                y = getHeight() - (int) mKeyguardBottomPadding;
-                mDebugPaint.setColor(Color.GRAY);
-                canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-            }
-
-            y = getHeight() - getEmptyBottomMargin();
-            mDebugPaint.setColor(Color.GREEN);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = (int) (mAmbientState.getStackY());
-            mDebugPaint.setColor(Color.CYAN);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-
-            y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
-            mDebugPaint.setColor(Color.BLUE);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+            onDrawDebug(canvas);
         }
     }
 
+    /** Used to track the Y positions that were already used to draw debug text labels. */
+    private static final Set<Integer> DEBUG_TEXT_USED_Y_POSITIONS =
+            DEBUG ? new HashSet<>() : Collections.emptySet();
+
+    private void onDrawDebug(Canvas canvas) {
+        DEBUG_TEXT_USED_Y_POSITIONS.clear();
+
+        int y = mTopPadding;
+        drawDebugInfo(canvas, y, Color.RED, /* label= */ "mTopPadding");
+
+        y = getLayoutHeight();
+        drawDebugInfo(canvas, y, Color.YELLOW, /* label= */ "getLayoutHeight()");
+
+        y = (int) mMaxLayoutHeight;
+        drawDebugInfo(canvas, y, Color.MAGENTA, /* label= */ "mMaxLayoutHeight");
+
+        if (mKeyguardBottomPadding >= 0) {
+            y = getHeight() - (int) mKeyguardBottomPadding;
+            drawDebugInfo(canvas, y, Color.GRAY,
+                    /* label= */ "getHeight() - mKeyguardBottomPadding");
+        }
+
+        y = getHeight() - getEmptyBottomMargin();
+        drawDebugInfo(canvas, y, Color.GREEN, /* label= */ "getHeight() - getEmptyBottomMargin()");
+
+        y = (int) (mAmbientState.getStackY());
+        drawDebugInfo(canvas, y, Color.CYAN, /* label= */ "mAmbientState.getStackY()");
+
+        y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+        drawDebugInfo(canvas, y, Color.BLUE,
+                /* label= */ "mAmbientState.getStackY() + mAmbientState.getStackHeight()");
+    }
+
+    private void drawDebugInfo(Canvas canvas, int y, int color, String label) {
+        mDebugPaint.setColor(color);
+        canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ getWidth(), /* stopY= */ y,
+                mDebugPaint);
+        canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+    }
+
+    private int computeDebugYTextPosition(int lineY) {
+        int textY = lineY;
+        while (DEBUG_TEXT_USED_Y_POSITIONS.contains(textY)) {
+            textY = (int) (textY + mDebugPaint.getTextSize());
+        }
+        DEBUG_TEXT_USED_Y_POSITIONS.add(textY);
+        return textY;
+    }
+
     @ShadeViewRefactor(RefactorComponent.DECORATOR)
     private void drawBackground(Canvas canvas) {
         int lockScreenLeft = mSidePaddings;
@@ -1088,6 +1112,7 @@
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     private void updateAlgorithmHeightAndPadding() {
         mAmbientState.setLayoutHeight(getLayoutHeight());
+        mAmbientState.setLayoutMaxHeight(mMaxLayoutHeight);
         updateAlgorithmLayoutMinHeight();
         mAmbientState.setTopPadding(mTopPadding);
     }
@@ -3949,6 +3974,7 @@
             updateChronometers();
             requestChildrenUpdate();
             updateUseRoundedRectClipping();
+            updateDismissBehavior();
         }
     }
 
@@ -4747,6 +4773,8 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setQsExpansionFraction(float qsExpansionFraction) {
+        boolean footerAffected = mQsExpansionFraction != qsExpansionFraction
+                && (mQsExpansionFraction == 1 || qsExpansionFraction == 1);
         mQsExpansionFraction = qsExpansionFraction;
         updateUseRoundedRectClipping();
 
@@ -4755,6 +4783,9 @@
         if (mOwnScrollY > 0) {
             setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
         }
+        if (footerAffected) {
+            updateFooter();
+        }
     }
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
@@ -4823,8 +4854,12 @@
 
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public int getMinExpansionHeight() {
+        // shelf height is defined in dp but status bar height can be defined in px, that makes
+        // relation between them variable - sometimes one might be bigger than the other when
+        // changing density. That’s why we need to ensure we’re not subtracting negative value below
         return mShelf.getIntrinsicHeight()
-                - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2
+                - Math.max(0,
+                (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2)
                 + mWaterfallTopInset;
     }
 
@@ -4907,6 +4942,10 @@
         StringBuilder sb = new StringBuilder("[")
                 .append(this.getClass().getSimpleName()).append(":")
                 .append(" pulsing=").append(mPulsing ? "T" : "f")
+                .append(" expanded=").append(mIsExpanded ? "T" : "f")
+                .append(" headsUpPinned=").append(mInHeadsUpPinnedMode ? "T" : "f")
+                .append(" qsClipping=").append(mShouldUseRoundedRectClipping ? "T" : "f")
+                .append(" qsClipDismiss=").append(mDismissUsingRowTranslationX ? "T" : "f")
                 .append(" visibility=").append(DumpUtilsKt.visibilityString(getVisibility()))
                 .append(" alpha=").append(getAlpha())
                 .append(" scrollY=").append(mAmbientState.getScrollY())
@@ -5434,7 +5473,7 @@
         // On the split keyguard, dismissing with clipping without a visual boundary looks odd,
         // so let's use the content dismiss behavior instead.
         boolean dismissUsingRowTranslationX = !mShouldUseSplitNotificationShade
-                || mStatusBarState != StatusBarState.KEYGUARD;
+                || (mStatusBarState != StatusBarState.KEYGUARD && mIsExpanded);
         if (mDismissUsingRowTranslationX != dismissUsingRowTranslationX) {
             mDismissUsingRowTranslationX = dismissUsingRowTranslationX;
             for (int i = 0; i < getChildCount(); i++) {
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 015edb8..2c70a5f 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
@@ -29,6 +29,7 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -60,6 +61,7 @@
     @VisibleForTesting float mHeadsUpInset;
     private int mPinnedZTranslationExtra;
     private float mNotificationScrimPadding;
+    private int mCloseHandleUnderlapHeight;
 
     public StackScrollAlgorithm(
             Context context,
@@ -85,6 +87,7 @@
                 R.dimen.heads_up_pinned_elevation);
         mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height);
         mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
+        mCloseHandleUnderlapHeight = res.getDimensionPixelSize(R.dimen.close_handle_underlap);
     }
 
     /**
@@ -459,13 +462,17 @@
                                 && !hasOngoingNotifs(algorithmState));
             }
         } else {
-            if (view != ambientState.getTrackedHeadsUpRow()) {
+            if (view instanceof EmptyShadeView) {
+                float fullHeight = ambientState.getLayoutMaxHeight() + mCloseHandleUnderlapHeight
+                        - ambientState.getStackY();
+                viewState.yTranslation = (fullHeight - getMaxAllowedChildHeight(view)) / 2f;
+            } else if (view != ambientState.getTrackedHeadsUpRow()) {
                 if (ambientState.isExpansionChanging()) {
                     // We later update shelf state, then hide views below the shelf.
                     viewState.hidden = false;
                     viewState.inShelf = algorithmState.firstViewInShelf != null
                             && i >= algorithmState.visibleChildren.indexOf(
-                                    algorithmState.firstViewInShelf);
+                            algorithmState.firstViewInShelf);
                 } else if (ambientState.getShelf() != null) {
                     // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
                     // to shelf start, thereby hiding all notifications (except the first one, which
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index e3a4bf0..2dc9276 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -51,6 +51,7 @@
     public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
     public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
     public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
+    public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600;
     public static final int ANIMATION_DURATION_PULSE_APPEAR =
             KeyguardSliceView.DEFAULT_ANIM_DURATION;
     public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
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 aa3b3e1..ad1c232 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -74,7 +74,7 @@
     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();
-    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 3;
+    private static final int FP_ATTEMPTS_BEFORE_SHOW_BOUNCER = 2;
 
     @IntDef(prefix = { "MODE_" }, value = {
             MODE_NONE,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 14f7134..d87a024 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -68,7 +68,7 @@
     private final Resources mResources;
     private final BatteryController mBatteryController;
     private final FeatureFlags mFeatureFlags;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
 
     private final Set<Callback> mCallbacks = new HashSet<>();
 
@@ -85,7 +85,7 @@
             TunerService tunerService,
             DumpManager dumpManager,
             FeatureFlags featureFlags,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+            ScreenOffAnimationController screenOffAnimationController) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
         mAlwaysOnPolicy = alwaysOnDisplayPolicy;
@@ -96,7 +96,7 @@
         mPowerManager = powerManager;
         mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
         mFeatureFlags = featureFlags;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
 
         tunerService.addTunable(
                 this,
@@ -228,7 +228,23 @@
      * then abruptly showing AOD.
      */
     public boolean shouldControlUnlockedScreenOff() {
-        return mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation();
+        return mScreenOffAnimationController.shouldControlUnlockedScreenOff();
+    }
+
+    public boolean shouldDelayKeyguardShow() {
+        return mScreenOffAnimationController.shouldDelayKeyguardShow();
+    }
+
+    public boolean shouldClampToDimBrightness() {
+        return mScreenOffAnimationController.shouldClampDozeScreenBrightness();
+    }
+
+    public boolean shouldShowLightRevealScrim() {
+        return mScreenOffAnimationController.shouldShowLightRevealScrim();
+    }
+
+    public boolean shouldAnimateDozingChange() {
+        return mScreenOffAnimationController.shouldAnimateDozingChange();
     }
 
     /**
@@ -312,6 +328,7 @@
         for (Callback callback : mCallbacks) {
             callback.onAlwaysOnChange();
         }
+        mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
     }
 
     @Override
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 8a7cf36..ac62522 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.view.View;
 
@@ -59,9 +58,7 @@
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationStackScrollLayoutController mStackScrollerController;
-    private final View mCenteredIconView;
-    private final View mClockView;
-    private final View mOperatorNameView;
+
     private final DarkIconDispatcher mDarkIconDispatcher;
     private final NotificationPanelViewController mNotificationPanelViewController;
     private final Consumer<ExpandableNotificationRow>
@@ -71,6 +68,11 @@
     private final StatusBarStateController mStatusBarStateController;
     private final CommandQueue mCommandQueue;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
+
+    private View mCenteredIconView;
+    private View mClockView;
+    private View mOperatorNameView;
+
     @VisibleForTesting
     float mExpandedHeight;
     @VisibleForTesting
@@ -85,7 +87,6 @@
                 }
             };
     private boolean mAnimationsEnabled = true;
-    Point mPoint;
     private KeyguardStateController mKeyguardStateController;
 
     @Inject
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 e7d5724..8187163 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -392,7 +392,7 @@
         }
 
         float alphaQsExpansion = 1 - Math.min(
-                1, mNotificationPanelViewStateProvider.getQsExpansionFraction() * 2);
+                1, mNotificationPanelViewStateProvider.getLockscreenShadeDragProgress() * 2);
         float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
                 * mKeyguardStatusBarAnimateAlpha
                 * (1.0f - mKeyguardHeadsUpShowingAmount);
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 68ab077..c61510c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -18,9 +18,10 @@
 
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 
+import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.LIGHTS_OUT_NOTIF_VIEW;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.annotation.Nullable;
 import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowInsetsController.Appearance;
@@ -28,105 +29,94 @@
 import android.view.WindowManager;
 import android.view.animation.AccelerateInterpolator;
 
+import androidx.lifecycle.Observer;
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.util.ViewController;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
- * Apps can request a low profile mode {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
+ * Apps can request a low profile mode {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}
  * where status bar and navigation icons dim. In this mode, a notification dot appears
  * where the notification icons would appear if they would be shown outside of this mode.
  *
  * This controller shows and hides the notification dot in the status bar to indicate
- * whether there are notifications when the device is in {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}.
+ * whether there are notifications when the device is in {@link View#SYSTEM_UI_FLAG_LOW_PROFILE}.
  */
-@SysUISingleton
-public class LightsOutNotifController {
+@StatusBarFragmentScope
+public class LightsOutNotifController extends ViewController<View> {
     private final CommandQueue mCommandQueue;
-    private final NotificationEntryManager mEntryManager;
+    private final NotifLiveDataStore mNotifDataStore;
     private final WindowManager mWindowManager;
+    private final Observer<Boolean> mObserver = hasNotifs -> updateLightsOutView();
 
-    /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
+    /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
     @VisibleForTesting @Appearance int mAppearance;
 
     private int mDisplayId;
-    private View mLightsOutNotifView;
 
     @Inject
-    LightsOutNotifController(WindowManager windowManager,
-            NotificationEntryManager entryManager,
+    LightsOutNotifController(
+            @Named(LIGHTS_OUT_NOTIF_VIEW) View lightsOutNotifView,
+            WindowManager windowManager,
+            NotifLiveDataStore notifDataStore,
             CommandQueue commandQueue) {
+        super(lightsOutNotifView);
         mWindowManager = windowManager;
-        mEntryManager = entryManager;
+        mNotifDataStore = notifDataStore;
         mCommandQueue = commandQueue;
+
     }
 
-    /**
-     * Sets the notification dot view after it is created in the StatusBar.
-     * This is the view this controller will show and hide depending on whether:
-     * 1. there are active notifications
-     * 2. an app has requested {@link View.SYSTEM_UI_FLAG_LOW_PROFILE}
-     */
-    void setLightsOutNotifView(View lightsOutNotifView) {
-        destroy();
-        mLightsOutNotifView = lightsOutNotifView;
-
-        if (mLightsOutNotifView != null) {
-            mLightsOutNotifView.setVisibility(View.GONE);
-            mLightsOutNotifView.setAlpha(0f);
-            init();
-        }
-    }
-
-    private void destroy() {
-        mEntryManager.removeNotificationEntryListener(mEntryListener);
+    @Override
+    protected void onViewDetached() {
+        mNotifDataStore.getHasActiveNotifs().removeObserver(mObserver);
         mCommandQueue.removeCallback(mCallback);
     }
 
-    private void init() {
+    @Override
+    protected void onViewAttached() {
+        mView.setVisibility(View.GONE);
+        mView.setAlpha(0f);
+
         mDisplayId = mWindowManager.getDefaultDisplay().getDisplayId();
-        mEntryManager.addNotificationEntryListener(mEntryListener);
+        mNotifDataStore.getHasActiveNotifs().addSyncObserver(mObserver);
         mCommandQueue.addCallback(mCallback);
 
         updateLightsOutView();
     }
 
     private boolean hasActiveNotifications() {
-        return mEntryManager.hasActiveNotifications();
+        return mNotifDataStore.getHasActiveNotifs().getValue();
     }
 
     @VisibleForTesting
     void updateLightsOutView() {
-        if (mLightsOutNotifView == null) {
-            return;
-        }
-
         final boolean showDot = shouldShowDot();
         if (showDot != isShowingDot()) {
             if (showDot) {
-                mLightsOutNotifView.setAlpha(0f);
-                mLightsOutNotifView.setVisibility(View.VISIBLE);
+                mView.setAlpha(0f);
+                mView.setVisibility(View.VISIBLE);
             }
 
-            mLightsOutNotifView.animate()
+            mView.animate()
                     .alpha(showDot ? 1 : 0)
                     .setDuration(showDot ? 750 : 250)
                     .setInterpolator(new AccelerateInterpolator(2.0f))
                     .setListener(new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator a) {
-                            mLightsOutNotifView.setAlpha(showDot ? 1 : 0);
-                            mLightsOutNotifView.setVisibility(showDot ? View.VISIBLE : View.GONE);
+                            mView.setAlpha(showDot ? 1 : 0);
+                            mView.setVisibility(showDot ? View.VISIBLE : View.GONE);
                             // Unset the listener, otherwise this may persist for
                             // another view property animation
-                            mLightsOutNotifView.animate().setListener(null);
+                            mView.animate().setListener(null);
                         }
                     })
                     .start();
@@ -135,8 +125,8 @@
 
     @VisibleForTesting
     boolean isShowingDot() {
-        return mLightsOutNotifView.getVisibility() == View.VISIBLE
-                && mLightsOutNotifView.getAlpha() == 1.0f;
+        return mView.getVisibility() == View.VISIBLE
+                && mView.getAlpha() == 1.0f;
     }
 
     @VisibleForTesting
@@ -162,23 +152,4 @@
             updateLightsOutView();
         }
     };
-
-    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
-        // Cares about notifications post-filtering
-        @Override
-        public void onNotificationAdded(NotificationEntry entry) {
-            updateLightsOutView();
-        }
-
-        @Override
-        public void onPostEntryUpdated(NotificationEntry entry) {
-            updateLightsOutView();
-        }
-
-        @Override
-        public void onEntryRemoved(@Nullable NotificationEntry entry,
-                NotificationVisibility visibility, boolean removedByUser, int reason) {
-            updateLightsOutView();
-        }
-    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index e66ad61..5d6e807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -73,7 +73,7 @@
     private final DozeParameters mDozeParameters;
     private final Optional<Bubbles> mBubblesOptional;
     private final StatusBarWindowController mStatusBarWindowController;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
 
     private int mIconSize;
     private int mIconHPadding;
@@ -123,7 +123,7 @@
             DemoModeController demoModeController,
             DarkIconDispatcher darkIconDispatcher,
             StatusBarWindowController statusBarWindowController,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+            ScreenOffAnimationController screenOffAnimationController) {
         mContrastColorUtil = ContrastColorUtil.getInstance(context);
         mContext = context;
         mStatusBarStateController = statusBarStateController;
@@ -137,7 +137,7 @@
         mDemoModeController = demoModeController;
         mDemoModeController.addCallback(this);
         mStatusBarWindowController = statusBarWindowController;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         notificationListener.addNotificationSettingsListener(mSettingsListener);
 
         initializeNotificationAreaViews(context);
@@ -617,7 +617,7 @@
         if (mAodIcons == null) {
             return;
         }
-        if (mDozeParameters.shouldControlScreenOff()) {
+        if (mScreenOffAnimationController.shouldAnimateAodIcons()) {
             mAodIcons.setTranslationY(-mAodIconAppearTranslation);
             mAodIcons.setAlpha(0);
             animateInAodIconTranslation();
@@ -689,7 +689,7 @@
         // playing, in which case we want them to be visible since we're animating in the AOD UI and
         // will be switching to KEYGUARD shortly.
         if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD
-                && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+                && !mScreenOffAnimationController.shouldShowAodIconsWhenShade()) {
             visible = false;
         }
         if (visible && mWakeUpCoordinator.isPulseExpanding()
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 4651e8446..c68d39b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -66,11 +66,7 @@
 
     @Override
     public RankingMap getCurrentRanking() {
-        RankingMap currentRanking = super.getCurrentRanking();
-        for (NotificationListenerController plugin : mPlugins) {
-            currentRanking = plugin.getCurrentRanking(currentRanking);
-        }
-        return currentRanking;
+        return onPluginRankingUpdate(super.getCurrentRanking());
     }
 
     public void onPluginConnected() {
@@ -120,8 +116,11 @@
         return false;
     }
 
-    public RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
-        return getCurrentRanking();
+    protected RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
+        for (NotificationListenerController plugin : mPlugins) {
+            rankingMap = plugin.getCurrentRanking(rankingMap);
+        }
+        return rankingMap;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 275074d..434671c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.view.View.GONE;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static androidx.constraintlayout.widget.ConstraintSet.END;
 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
@@ -26,14 +27,15 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.keyguard.KeyguardClockSwitch.LARGE;
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 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.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
@@ -109,6 +111,7 @@
 import com.android.systemui.DejankUtils;
 import com.android.systemui.Dependency;
 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;
@@ -238,7 +241,8 @@
      */
     private static final int FLING_HIDE = 2;
     private static final long ANIMATION_DELAY_ICON_FADE_IN =
-            LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION
+            ActivityLaunchAnimator.TIMINGS.getTotalDuration()
+                    - CollapsedStatusBarFragment.FADE_IN_DURATION
                     - CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
 
     private final DozeParameters mDozeParameters;
@@ -351,7 +355,7 @@
     private NotificationsQSContainerController mNotificationsQSContainerController;
     private boolean mAnimateNextPositionUpdate;
     private float mQuickQsOffsetHeight;
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private ScreenOffAnimationController mScreenOffAnimationController;
 
     private int mTrackingPointer;
     private VelocityTracker mQsVelocityTracker;
@@ -462,6 +466,9 @@
     private boolean mIsFullWidth;
     private boolean mBlockingExpansionForCurrentTouch;
 
+    // TODO (b/204204226): no longer needed once refactor is complete
+    private final boolean mUseCombinedQSHeaders;
+
     /**
      * Following variables maintain state of events when input focus transfer may occur.
      */
@@ -517,8 +524,6 @@
 
     private WeakReference<CommunalSource> mCommunalSource;
 
-    private final CommunalSource.Callback mCommunalSourceCallback;
-
     private final CommandQueue mCommandQueue;
     private final NotificationLockscreenUserManager mLockscreenUserManager;
     private final UserManager mUserManager;
@@ -771,7 +776,7 @@
             @Main Executor uiExecutor,
             SecureSettings secureSettings,
             SplitShadeHeaderController splitShadeHeaderController,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             LockscreenGestureLogger lockscreenGestureLogger,
             PanelExpansionStateManager panelExpansionStateManager,
             NotificationRemoteInputManager remoteInputManager,
@@ -780,6 +785,7 @@
             InteractionJankMonitor interactionJankMonitor,
             QsFrameTranslateController qsFrameTranslateController) {
         super(view,
+                featureFlags,
                 falsingManager,
                 dozeLog,
                 keyguardStateController,
@@ -881,7 +887,7 @@
         mConversationNotificationManager = conversationNotificationManager;
         mAuthController = authController;
         mLockIconViewController = lockIconViewController;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         mRemoteInputManager = remoteInputManager;
 
         int currentMode = navigationModeController.addListener(
@@ -903,9 +909,6 @@
 
         mMaxKeyguardNotifications = resources.getInteger(R.integer.keyguard_max_notification_count);
         mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
-        mCommunalSourceCallback = () -> {
-            mUiExecutor.execute(() -> setCommunalSource(null /*source*/));
-        };
 
         mCommunalSourceMonitorCallback = (source) -> {
             mUiExecutor.execute(() -> setCommunalSource(source));
@@ -913,6 +916,8 @@
         mQsFrameTranslateController = qsFrameTranslateController;
         updateUserSwitcherFlags();
         onFinishInflate();
+
+        mUseCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS);
     }
 
     private void onFinishInflate() {
@@ -1142,6 +1147,9 @@
         } else {
             constraintSet.connect(R.id.qs_frame, END, PARENT_ID, END);
             constraintSet.connect(R.id.notification_stack_scroller, START, PARENT_ID, START);
+            if (mUseCombinedQSHeaders) {
+                constraintSet.constrainHeight(R.id.split_shade_status_bar, WRAP_CONTENT);
+            }
         }
         constraintSet.getConstraint(R.id.notification_stack_scroller).layout.mWidth = panelWidth;
         constraintSet.getConstraint(R.id.qs_frame).layout.mWidth = qsWidth;
@@ -1392,7 +1400,6 @@
             stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
         }
 
-        mSplitShadeHeaderController.setShadeExpandedFraction(getExpandedFraction());
         mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
         mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
 
@@ -1408,7 +1415,7 @@
         }
 
         float expandedFraction =
-                mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : getExpandedFraction();
         mCommunalPositionAlgorithm.setup(expandedFraction, mCommunalView.getHeight());
         mCommunalPositionAlgorithm.run(mCommunalPositionResult);
@@ -1425,20 +1432,21 @@
                 .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMedia();
         boolean splitShadeWithActiveMedia =
                 mShouldUseSplitNotificationShade && mMediaDataManager.hasActiveMedia();
+        boolean shouldAnimateClockChange = mScreenOffAnimationController.shouldAnimateClockChange();
         if ((hasVisibleNotifications && !mShouldUseSplitNotificationShade)
                 || (splitShadeWithActiveMedia && !mDozing)) {
-            mKeyguardStatusViewController.displayClock(SMALL);
+            mKeyguardStatusViewController.displayClock(SMALL, shouldAnimateClockChange);
         } else {
-            mKeyguardStatusViewController.displayClock(LARGE);
+            mKeyguardStatusViewController.displayClock(LARGE, shouldAnimateClockChange);
         }
         updateKeyguardStatusViewAlignment(true /* animate */);
         int userIconHeight = mKeyguardQsUserSwitchController != null
                 ? mKeyguardQsUserSwitchController.getUserIconHeight() : 0;
         float expandedFraction =
-                mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : getExpandedFraction();
         float darkamount =
-                mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+                mScreenOffAnimationController.shouldExpandNotifications()
                         ? 1.0f : mInterpolatedDarkAmount;
 
         float udfpsAodTopLocation = -1f;
@@ -1465,7 +1473,7 @@
                 mKeyguardStatusViewController.isClockTopAligned());
         mClockPositionAlgorithm.run(mClockPositionResult);
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
-        boolean animateClock = animate || mAnimateNextPositionUpdate;
+        boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
         mKeyguardStatusViewController.updatePosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY,
                 mClockPositionResult.clockScale, animateClock);
@@ -2392,12 +2400,14 @@
 
     private void updateQsExpansion() {
         if (mQs == null) return;
-        float qsExpansionFraction = computeQsExpansionFraction();
-        float squishiness = mNotificationStackScrollLayoutController
-                .getNotificationSquishinessFraction();
-        mQs.setQsExpansion(qsExpansionFraction, getExpandedFraction(), getHeaderTranslation(),
-                mQsExpandImmediate || mQsExpanded ? 1f : squishiness);
-        mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
+        final float squishiness =
+                mQsExpandImmediate || mQsExpanded ? 1f : mNotificationStackScrollLayoutController
+                        .getNotificationSquishinessFraction();
+        final float qsExpansionFraction = computeQsExpansionFraction();
+        final float adjustedExpansionFraction = mShouldUseSplitNotificationShade
+                ? 1f : computeQsExpansionFraction();
+        mQs.setQsExpansion(adjustedExpansionFraction, getExpandedFraction(), getHeaderTranslation(),
+                squishiness);
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
         int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
@@ -2411,6 +2421,17 @@
 
         mDepthController.setQsPanelExpansion(qsExpansionFraction);
 
+        // updateQsExpansion will get called whenever mTransitionToFullShadeProgress or
+        // mLockscreenShadeTransitionController.getDragProgress change.
+        // When in lockscreen, getDragProgress indicates the true expanded fraction of QS
+        float shadeExpandedFraction = mTransitioningToFullShadeProgress > 0
+                ? mLockscreenShadeTransitionController.getDragProgress()
+                : getExpandedFraction();
+        mSplitShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
+        mSplitShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
+        mSplitShadeHeaderController.setShadeExpanded(mQsVisible);
+
+
         if (mCommunalViewController != null) {
             mCommunalViewController.updateQsExpansion(qsExpansionFraction);
         }
@@ -3413,12 +3434,6 @@
                     mStatusBarStateController.setState(KEYGUARD);
                 }
                 return true;
-            case StatusBarState.SHADE:
-
-                // This gets called in the middle of the touch handling, where the state is still
-                // that we are tracking the panel. Collapse the panel after this is done.
-                mView.post(mPostCollapseRunnable);
-                return false;
             default:
                 return true;
         }
@@ -3631,11 +3646,15 @@
         return !isFullWidth() || !mShowIconsWhenExpanded;
     }
 
-    public final QS.ScrollListener mScrollListener = scrollY -> {
-        if (scrollY > 0 && !mQsFullyExpanded) {
-            if (DEBUG) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
-            // If we are scrolling QS, we should be fully expanded.
-            expandWithQs();
+    public final QS.ScrollListener mScrollListener = new QS.ScrollListener() {
+        @Override
+        public void onQsPanelScrollChanged(int scrollY) {
+            mSplitShadeHeaderController.setQsScrollY(scrollY);
+            if (scrollY > 0 && !mQsFullyExpanded) {
+                if (DEBUG) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
+                // If we are scrolling QS, we should be fully expanded.
+                expandWithQs();
+            }
         }
     };
 
@@ -3782,8 +3801,8 @@
     }
 
     public void applyLaunchAnimationProgress(float linearProgress) {
-        boolean hideIcons = LaunchAnimator.getProgress(linearProgress,
-                ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+        boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
+                linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
         if (hideIcons != mHideIconsDuringLaunchAnimation) {
             mHideIconsDuringLaunchAnimation = hideIcons;
             if (!hideIcons) {
@@ -3826,6 +3845,45 @@
         }
     }
 
+    /**
+     * 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();
+
+        // Move the content of the AOD all the way to the left
+        // so we can animate to the initial position
+        final int translationAmount = mView.getResources().getDimensionPixelSize(
+                R.dimen.below_clock_padding_start);
+        mView.setTranslationX(-translationAmount);
+        mView.setAlpha(0);
+    }
+
+    /**
+     * Starts fold to AOD animation
+     */
+    public void startFoldToAodAnimation(Runnable endAction) {
+        mView.animate()
+            .translationX(0)
+            .alpha(1f)
+            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+            .setInterpolator(EMPHASIZED_DECELERATE)
+            .setListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    endAction.run();
+                }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endAction.run();
+                }
+            })
+            .start();
+
+        mKeyguardStatusViewController.animateFoldToAod();
+    }
+
     /** */
     public void setImportantForAccessibility(int mode) {
         mView.setImportantForAccessibility(mode);
@@ -3940,6 +3998,10 @@
         mView.setAlpha(alpha);
     }
 
+    public void resetTranslation() {
+        mView.setTranslationX(0f);
+    }
+
     public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
         return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
                 durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
@@ -4557,7 +4619,7 @@
             int oldState = mBarState;
             boolean keyguardShowing = statusBarState == KEYGUARD;
 
-            if (mDozeParameters.shouldControlUnlockedScreenOff()
+            if (mDozeParameters.shouldDelayKeyguardShow()
                     && oldState == StatusBarState.SHADE
                     && statusBarState == KEYGUARD) {
                 // This means we're doing the screen off animation - position the keyguard status
@@ -4619,7 +4681,7 @@
             } else {
                 final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
                         && statusBarState == KEYGUARD
-                        && mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
+                        && 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.
@@ -4643,8 +4705,6 @@
             // would reset
             maybeAnimateBottomAreaAlpha();
             updateQsState();
-            mSplitShadeHeaderController.setShadeExpanded(
-                    mBarState == SHADE || mBarState == SHADE_LOCKED);
         }
 
         @Override
@@ -4674,6 +4734,9 @@
          * {@link KeyguardStatusBarViewController} and remove this method.
          */
         boolean shouldHeadsUpBeVisible();
+
+        /** Return the fraction of the shade that's expanded, when in lockscreen. */
+        float getLockscreenShadeDragProgress();
     }
 
     private final NotificationPanelViewStateProvider mNotificationPanelViewStateProvider =
@@ -4692,6 +4755,11 @@
                 public boolean shouldHeadsUpBeVisible() {
                     return mHeadsUpAppearanceController.shouldBeVisible();
                 }
+
+                @Override
+                public float getLockscreenShadeDragProgress() {
+                    return mLockscreenShadeTransitionController.getDragProgress();
+                }
             };
 
     /**
@@ -4713,7 +4781,6 @@
         CommunalSource existingSource = mCommunalSource != null ? mCommunalSource.get() : null;
 
         if (existingSource != null) {
-            existingSource.removeCallback(mCommunalSourceCallback);
             mCommunalViewController.show(null /*source*/);
         }
 
@@ -4722,7 +4789,6 @@
         CommunalSource currentSource = mCommunalSource != null ? mCommunalSource.get() : null;
         // Set source and register callback
         if (currentSource != null && mCommunalViewController != null) {
-            currentSource.addCallback(mCommunalSourceCallback);
             mCommunalViewController.show(source);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index e1cb9f6..c859e70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -109,7 +109,7 @@
             mCallbacks = new ArrayList<>();
 
     private final SysuiColorExtractor mColorExtractor;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
     private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
 
     @Inject
@@ -122,7 +122,7 @@
             SysuiColorExtractor colorExtractor,
             DumpManager dumpManager,
             KeyguardStateController keyguardStateController,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             AuthController authController) {
         mContext = context;
         mWindowManager = windowManager;
@@ -134,7 +134,7 @@
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardBypassController = keyguardBypassController;
         mColorExtractor = colorExtractor;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         dumpManager.registerDumpable(getClass().getName(), this);
         mAuthController = authController;
 
@@ -344,7 +344,7 @@
                 // Make the panel focusable if we're doing the screen off animation, since the light
                 // reveal scrim is drawing in the panel and should consume touch events so that they
                 // don't go to the app behind.
-                || mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+                || mScreenOffAnimationController.shouldIgnoreKeyguardTouches()) {
             mLpChanged.flags &= ~LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 55f1450..81d4bbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -98,7 +98,6 @@
     private boolean mExpandAnimationRunning;
     private NotificationStackScrollLayout mStackScrollLayout;
     private PhoneStatusBarView mStatusBarView;
-    private PhoneStatusBarTransitions mBarTransitions;
     private StatusBar mService;
     private NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
@@ -497,17 +496,8 @@
         }
     }
 
-    public PhoneStatusBarTransitions getBarTransitions() {
-        return mBarTransitions;
-    }
-
     public void setStatusBarView(PhoneStatusBarView statusBarView) {
         mStatusBarView = statusBarView;
-        if (statusBarView != null) {
-            mBarTransitions = new PhoneStatusBarTransitions(
-                    statusBarView,
-                    mStatusBarWindowController.getBackgroundView());
-        }
     }
 
     public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 6a00591..53bfd77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -53,6 +53,8 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.doze.DozeLog;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -212,6 +214,7 @@
 
     public PanelViewController(
             PanelView view,
+            FeatureFlags featureFlags,
             FalsingManager falsingManager,
             DozeLog dozeLog,
             KeyguardStateController keyguardStateController,
@@ -270,8 +273,7 @@
         mBounceInterpolator = new BounceInterpolator();
         mFalsingManager = falsingManager;
         mDozeLog = dozeLog;
-        mNotificationsDragEnabled = mResources.getBoolean(
-                R.bool.config_enableNotificationShadeDrag);
+        mNotificationsDragEnabled = featureFlags.isEnabled(Flags.NOTIFICATION_SHADE_DRAG);
         mVibratorHelper = vibratorHelper;
         mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
@@ -931,7 +933,6 @@
 
     private void abortAnimations() {
         cancelHeightAnimator();
-        mView.removeCallbacks(mPostCollapseRunnable);
         mView.removeCallbacks(mFlingCollapseRunnable);
     }
 
@@ -1108,13 +1109,6 @@
         return onMiddleClicked();
     }
 
-    protected final Runnable mPostCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            collapse(false /* delayed */, 1.0f /* speedUpFactor */);
-        }
-    };
-
     protected abstract boolean onMiddleClicked();
 
     protected abstract boolean isDozing();
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 f67d181..1e71ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,7 +38,6 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.window.StatusBarWindowView;
 import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.Objects;
@@ -76,6 +75,7 @@
 
     @Override
     public void onFinishInflate() {
+        super.onFinishInflate();
         mBattery = findViewById(R.id.battery);
         mClock = findViewById(R.id.clock);
         mCutoutSpace = findViewById(R.id.cutout_space_view);
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 ec7e93b..b9386bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -15,12 +15,14 @@
  */
 package com.android.systemui.statusbar.phone
 
+import android.content.res.Configuration
 import android.graphics.Point
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
 import com.android.systemui.R
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+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
@@ -35,9 +37,16 @@
     view: PhoneStatusBarView,
     @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
-    touchEventHandler: PhoneStatusBarView.TouchEventHandler
+    touchEventHandler: PhoneStatusBarView.TouchEventHandler,
+    private val configurationController: ConfigurationController
 ) : ViewController<PhoneStatusBarView>(view) {
 
+    private val configurationListener = object : ConfigurationController.ConfigurationListener {
+        override fun onConfigChanged(newConfig: Configuration?) {
+            mView.updateResources()
+        }
+    }
+
     override fun onViewAttached() {
         moveFromCenterAnimationController?.let { animationController ->
             val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
@@ -66,11 +75,13 @@
         }
 
         progressProvider?.setReadyToHandleTransition(true)
+        configurationController.addCallback(configurationListener)
     }
 
     override fun onViewDetached() {
         progressProvider?.setReadyToHandleTransition(false)
         moveFromCenterAnimationController?.onViewDetached()
+        configurationController.removeCallback(configurationListener)
     }
 
     init {
@@ -116,7 +127,8 @@
     class Factory @Inject constructor(
         private val unfoldComponent: Optional<SysUIUnfoldComponent>,
         @Named(UNFOLD_STATUS_BAR)
-        private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>
+        private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
+        private val configurationController: ConfigurationController
     ) {
         fun create(
             view: PhoneStatusBarView,
@@ -128,7 +140,8 @@
                 unfoldComponent.map {
                     it.getStatusBarMoveFromCenterAnimationController()
                 }.getOrNull(),
-                touchEventHandler
+                touchEventHandler,
+                configurationController
             )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
new file mode 100644
index 0000000..e806ca0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.unfold.FoldAodAnimationController
+import com.android.systemui.unfold.SysUIUnfoldComponent
+import java.util.Optional
+import javax.inject.Inject
+
+@SysUISingleton
+class ScreenOffAnimationController @Inject constructor(
+    sysUiUnfoldComponent: Optional<SysUIUnfoldComponent>,
+    unlockedScreenOffAnimation: UnlockedScreenOffAnimationController,
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+) : WakefulnessLifecycle.Observer {
+
+    private val foldToAodAnimation: FoldAodAnimationController? = sysUiUnfoldComponent
+        .orElse(null)?.getFoldAodAnimationController()
+
+    private val animations: List<ScreenOffAnimation> =
+        listOfNotNull(foldToAodAnimation, unlockedScreenOffAnimation)
+
+    fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
+        animations.forEach { it.initialize(statusBar, lightRevealScrim) }
+        wakefulnessLifecycle.addObserver(this)
+    }
+
+    /**
+     * Called when system reports that we are going to sleep
+     */
+    override fun onStartedGoingToSleep() {
+        animations.firstOrNull { it.startAnimation() }
+    }
+
+    /**
+     * Called when opaqueness of the light reveal scrim has change
+     * When [isOpaque] is true then scrim is visible and covers the screen
+     */
+    fun onScrimOpaqueChanged(isOpaque: Boolean) =
+        animations.forEach { it.onScrimOpaqueChanged(isOpaque) }
+
+    /**
+     * Called when always on display setting changed
+     */
+    fun onAlwaysOnChanged(alwaysOn: Boolean) =
+        animations.forEach { it.onAlwaysOnChanged(alwaysOn) }
+
+    /**
+     * If returns true we are taking over the screen off animation from display manager to SysUI.
+     * We can play our custom animation instead of default fade out animation.
+     */
+    fun shouldControlUnlockedScreenOff(): Boolean =
+        animations.any { it.shouldPlayAnimation() }
+
+    /**
+     * If returns true it fully expands notification shade, it could be used to make
+     * the scrims visible
+     */
+    fun shouldExpandNotifications(): Boolean =
+        animations.any { it.isAnimationPlaying() }
+
+    /**
+     * If returns true it allows to perform custom animation for showing
+     * keyguard in [animateInKeyguard]
+     */
+    fun shouldAnimateInKeyguard(): Boolean =
+        animations.any { it.shouldAnimateInKeyguard() }
+
+    /**
+     * Called when keyguard is about to be displayed and allows to perform custom animation
+     */
+    fun animateInKeyguard(keyguardView: View, after: Runnable) =
+        animations.firstOrNull {
+            if (it.shouldAnimateInKeyguard()) {
+                it.animateInKeyguard(keyguardView, after)
+                true
+            } else {
+                false
+            }
+        }
+
+    /**
+     * If returns true it will disable propagating touches to apps and keyguard
+     */
+    fun shouldIgnoreKeyguardTouches(): Boolean =
+        animations.any { it.isAnimationPlaying() }
+
+    /**
+     * If returns true wake up won't be blocked when dozing
+     */
+    fun allowWakeUpIfDozing(): Boolean =
+        animations.all { !it.isAnimationPlaying() }
+
+    /**
+     * Do not allow showing keyguard immediately so it could be postponed e.g. to the point when
+     * the animation ends
+     */
+    fun shouldDelayKeyguardShow(): Boolean =
+        animations.any { it.shouldPlayAnimation() }
+
+    /**
+     * Return true while we want to ignore requests to show keyguard, we need to handle pending
+     * keyguard lock requests manually
+     */
+    fun isKeyguardShowDelayed(): Boolean =
+        animations.any { it.isAnimationPlaying() }
+
+    /**
+     * Return true to ignore requests to hide keyguard
+     */
+    fun isKeyguardHideDelayed(): Boolean =
+        animations.any { it.isKeyguardHideDelayed() }
+
+    /**
+     * Return true to make the StatusBar expanded so we can animate [LightRevealScrim]
+     */
+    fun shouldShowLightRevealScrim(): Boolean =
+        animations.any { it.shouldPlayAnimation() }
+
+    /**
+     * Return true to indicate that we should hide [LightRevealScrim] when waking up
+     */
+    fun shouldHideLightRevealScrimOnWakeUp(): Boolean =
+        animations.any { it.shouldHideScrimOnWakeUp() }
+
+    /**
+     * Return true to override the dozing state of the notifications to fully dozing,
+     * so the notifications are not visible when playing the animation
+     */
+    fun overrideNotificationsFullyDozingOnKeyguard(): Boolean =
+        animations.any { it.overrideNotificationsDozeAmount() }
+
+    /**
+     * Return true to hide the notifications footer ('manage'/'clear all' buttons)
+     */
+    fun shouldHideNotificationsFooter(): Boolean =
+        animations.any { it.isAnimationPlaying() }
+
+    /**
+     * Return true to clamp screen brightness to 'dimmed' value when devices times out
+     * and goes to sleep
+     */
+    fun shouldClampDozeScreenBrightness(): Boolean =
+        animations.any { it.shouldPlayAnimation() }
+
+    /**
+     * Return true to show AOD icons even when status bar is in 'shade' state (unlocked)
+     */
+    fun shouldShowAodIconsWhenShade(): Boolean =
+        animations.any { it.shouldShowAodIconsWhenShade() }
+
+    /**
+     * Return true to allow animation of appearing AOD icons
+     */
+    fun shouldAnimateAodIcons(): Boolean =
+        animations.all { it.shouldAnimateAodIcons() }
+
+    /**
+     * Return true to animate doze state change, if returns false dozing will be applied without
+     * animation (sends only 0.0f or 1.0f dozing progress)
+     */
+    fun shouldAnimateDozingChange(): Boolean =
+        animations.all { it.shouldAnimateDozingChange() }
+
+    /**
+     * Return true to animate large <-> small clock transition
+     */
+    fun shouldAnimateClockChange(): Boolean =
+        animations.all { it.shouldAnimateClockChange() }
+}
+
+interface ScreenOffAnimation {
+    fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {}
+
+    /**
+     * Called when started going to sleep, should return true if the animation will be played
+     */
+    fun startAnimation(): Boolean = false
+
+    fun shouldPlayAnimation(): Boolean = false
+    fun isAnimationPlaying(): Boolean = false
+
+    fun onScrimOpaqueChanged(isOpaque: Boolean) {}
+    fun onAlwaysOnChanged(alwaysOn: Boolean) {}
+
+    fun shouldAnimateInKeyguard(): Boolean = false
+    fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
+
+    fun isKeyguardHideDelayed(): Boolean = false
+    fun shouldHideScrimOnWakeUp(): Boolean = false
+    fun overrideNotificationsDozeAmount(): Boolean = false
+    fun shouldShowAodIconsWhenShade(): Boolean = false
+    fun shouldAnimateAodIcons(): Boolean = true
+    fun shouldAnimateDozingChange(): Boolean = true
+    fun shouldAnimateClockChange(): Boolean = true
+}
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 1077347..a23e726e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -177,7 +177,7 @@
     private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
     private final Handler mHandler;
     private final Executor mMainExecutor;
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -234,7 +234,7 @@
             DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
             KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager,
             ConfigurationController configurationController, @Main Executor mainExecutor,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             PanelExpansionStateManager panelExpansionStateManager) {
         mScrimStateListener = lightBarController::setScrimState;
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -245,7 +245,7 @@
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
         mMainExecutor = mainExecutor;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
+        mScreenOffAnimationController = screenOffAnimationController;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
                 "hide_aod_wallpaper", mHandler);
         mWakeLock = delayedWakeLockBuilder.setHandler(mHandler).setTag("Scrims").build();
@@ -668,7 +668,7 @@
         if (mState == ScrimState.UNLOCKED) {
             // Darken scrim as you pull down the shade when unlocked, unless the shade is expanding
             // because we're doing the screen off animation.
-            if (!mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+            if (!mScreenOffAnimationController.shouldExpandNotifications()) {
                 float behindFraction = getInterpolatedFraction();
                 behindFraction = (float) Math.pow(behindFraction, 0.8f);
                 if (mClipsQsScrim) {
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 9246c0e..8afa637 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -191,7 +191,7 @@
             // by animating the screen off via the LightRevelScrim. In either case we just need to
             // set our state.
             mAnimateChange = mDozeParameters.shouldControlScreenOff()
-                    && !mDozeParameters.shouldControlUnlockedScreenOff();
+                    && !mDozeParameters.shouldShowLightRevealScrim();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index a54251a..b4fed2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -218,10 +218,6 @@
         return getStatusBar().getNotificationShadeWindowView();
     }
 
-    protected PhoneStatusBarView getStatusBarView() {
-        return (PhoneStatusBarView) getStatusBar().getStatusBarView();
-    }
-
     private NotificationPanelViewController getNotificationPanelViewController() {
         return getStatusBar().getPanelController();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
index 1ad9fa6..a1be5ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SplitShadeHeaderController.kt
@@ -18,17 +18,23 @@
 
 import android.view.View
 import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.settingslib.Utils
+import com.android.systemui.Dumpable
 import com.android.systemui.R
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
 import com.android.systemui.qs.HeaderPrivacyIconsController
 import com.android.systemui.qs.carrier.QSCarrierGroupController
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_BATTERY_CONTROLLER
 import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SPLIT_SHADE_HEADER
+import java.io.FileDescriptor
+import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Named
 
@@ -39,17 +45,29 @@
     private val privacyIconsController: HeaderPrivacyIconsController,
     qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
     featureFlags: FeatureFlags,
-    @Named(SPLIT_SHADE_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController
-) {
+    @Named(SPLIT_SHADE_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController,
+    dumpManager: DumpManager
+) : Dumpable {
 
     companion object {
         private val HEADER_TRANSITION_ID = R.id.header_transition
         private val SPLIT_HEADER_TRANSITION_ID = R.id.split_header_transition
+        private val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
+        private val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
+        private val SPLIT_HEADER_CONSTRAINT = R.id.split_header_constraint
+
+        private fun Int.stateToString() = when (this) {
+            QQS_HEADER_CONSTRAINT -> "QQS Header"
+            QS_HEADER_CONSTRAINT -> "QS Header"
+            SPLIT_HEADER_CONSTRAINT -> "Split Header"
+            else -> "Unknown state"
+        }
     }
 
     private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
-    // TODO(b/194178072) Handle RSSI hiding when multi carrier
-    private val iconManager: StatusBarIconController.IconManager
+    private val iconManager: StatusBarIconController.TintedIconManager
+    private val iconContainer: StatusIconContainer
+    private val carrierIconSlots: List<String>
     private val qsCarrierGroupController: QSCarrierGroupController
     private var visible = false
         set(value) {
@@ -95,16 +113,37 @@
             }
         }
 
+    var qsScrollY = 0
+        set(value) {
+            if (field != value) {
+                field = value
+                updateScrollY()
+            }
+        }
+
+    private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
+        override fun onChipVisibilityRefreshed(visible: Boolean) {
+            if (statusBar is MotionLayout) {
+                val state = statusBar.getConstraintSet(QQS_HEADER_CONSTRAINT).apply {
+                    setAlpha(R.id.statusIcons, if (visible) 0f else 1f)
+                    setAlpha(R.id.batteryRemainingIcon, if (visible) 0f else 1f)
+                }
+                statusBar.updateState(QQS_HEADER_CONSTRAINT, state)
+            }
+        }
+    }
+
     init {
         if (statusBar is MotionLayout) {
             val context = statusBar.context
             val resources = statusBar.resources
-            statusBar.getConstraintSet(R.id.qqs_header_constraint)
+            statusBar.getConstraintSet(QQS_HEADER_CONSTRAINT)
                     .load(context, resources.getXml(R.xml.qqs_header))
-            statusBar.getConstraintSet(R.id.qs_header_constraint)
+            statusBar.getConstraintSet(QS_HEADER_CONSTRAINT)
                     .load(context, resources.getXml(R.xml.qs_header))
-            statusBar.getConstraintSet(R.id.split_header_constraint)
+            statusBar.getConstraintSet(SPLIT_HEADER_CONSTRAINT)
                     .load(context, resources.getXml(R.xml.split_header))
+            privacyIconsController.chipVisibilityListener = chipVisibilityListener
         }
     }
 
@@ -116,15 +155,35 @@
         batteryMeterViewController.ignoreTunerUpdates()
         batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
 
-        val iconContainer: StatusIconContainer = statusBar.findViewById(R.id.statusIcons)
-        iconManager = StatusBarIconController.IconManager(iconContainer, featureFlags)
+        iconContainer = statusBar.findViewById(R.id.statusIcons)
+        iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
+        iconManager.setTint(Utils.getColorAttrDefaultColor(statusBar.context,
+                android.R.attr.textColorPrimary))
+
+        carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
+            listOf(
+                statusBar.context.getString(com.android.internal.R.string.status_bar_no_calling),
+                statusBar.context.getString(com.android.internal.R.string.status_bar_call_strength)
+            )
+        } else {
+            listOf(statusBar.context.getString(com.android.internal.R.string.status_bar_mobile))
+        }
         qsCarrierGroupController = qsCarrierGroupControllerBuilder
                 .setQSCarrierGroup(statusBar.findViewById(R.id.carrier_group))
                 .build()
+
+        dumpManager.registerDumpable(this)
+
         updateVisibility()
         updateConstraints()
     }
 
+    private fun updateScrollY() {
+        if (!splitShadeMode && combinedHeaders) {
+            statusBar.scrollY = qsScrollY
+        }
+    }
+
     private fun onShadeExpandedChanged() {
         if (shadeExpanded) {
             privacyIconsController.startListening()
@@ -136,7 +195,7 @@
     }
 
     private fun onSplitShadeModeChanged() {
-        if (splitShadeMode) {
+        if (splitShadeMode || combinedHeaders) {
             privacyIconsController.onParentVisible()
         } else {
             privacyIconsController.onParentInvisible()
@@ -170,6 +229,7 @@
             statusBar.setTransition(HEADER_TRANSITION_ID)
             statusBar.transitionToStart()
             updatePosition()
+            updateScrollY()
         }
     }
 
@@ -182,9 +242,33 @@
     private fun updateListeners() {
         qsCarrierGroupController.setListening(visible)
         if (visible) {
+            updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
+            qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
             statusBarIconController.addIconGroup(iconManager)
         } else {
+            qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
             statusBarIconController.removeIconGroup(iconManager)
         }
     }
+
+    private fun updateSingleCarrier(singleCarrier: Boolean) {
+        if (singleCarrier) {
+            iconContainer.removeIgnoredSlots(carrierIconSlots)
+        } else {
+            iconContainer.addIgnoredSlots(carrierIconSlots)
+        }
+    }
+
+    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+        pw.println("visible: $visible")
+        pw.println("shadeExpanded: $shadeExpanded")
+        pw.println("shadeExpandedFraction: $shadeExpandedFraction")
+        pw.println("splitShadeMode: $splitShadeMode")
+        pw.println("qsExpandedFraction: $qsExpandedFraction")
+        pw.println("qsScrollY: $qsScrollY")
+        if (combinedHeaders) {
+            statusBar as MotionLayout
+            pw.println("currentState: ${statusBar.currentState.stateToString()}")
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 2fe57185..07914cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -146,8 +146,8 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
+import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.emergency.EmergencyGesture;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -194,13 +194,12 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
-import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.PowerButtonReveal;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -220,9 +219,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -236,7 +232,6 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -346,7 +341,7 @@
         mStatusBarWindowState =  state;
         mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
         mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden);
-        if (getStatusBarView() != null) {
+        if (mStatusBarView != null) {
             // Should #updateHideIconsForBouncer always be called, regardless of whether we have a
             //   status bar view? If so, we can make #updateHideIconsForBouncer private.
             mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false);
@@ -439,6 +434,10 @@
         mCommandQueueCallbacks.animateCollapsePanels(flags, force);
     }
 
+    /** */
+    public void togglePanel() {
+        mCommandQueueCallbacks.togglePanel();
+    }
     /**
      * The {@link StatusBarState} of the status bar.
      */
@@ -457,13 +456,13 @@
     @Nullable
     protected LockscreenWallpaper mLockscreenWallpaper;
     private final AutoHideController mAutoHideController;
-    private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
 
     private final Point mCurrentDisplaySize = new Point();
 
     protected NotificationShadeWindowView mNotificationShadeWindowView;
     protected PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
+    private PhoneStatusBarTransitions mStatusBarTransitions;
     private AuthRippleController mAuthRippleController;
     private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected NotificationShadeWindowController mNotificationShadeWindowController;
@@ -499,7 +498,6 @@
     private final StatusBarNotificationActivityStarter.Builder
             mStatusBarNotificationActivityStarterBuilder;
     private final ShadeController mShadeController;
-    private final LightsOutNotifController mLightsOutNotifController;
     private final InitController mInitController;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
@@ -509,10 +507,7 @@
     private final DemoModeController mDemoModeController;
     private final NotificationsController mNotificationsController;
     private final OngoingCallController mOngoingCallController;
-    private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarSignalPolicy mStatusBarSignalPolicy;
-    private final StatusBarLocationPublisher mStatusBarLocationPublisher;
-    private final StatusBarIconController mStatusBarIconController;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
     // expanded notifications
@@ -522,7 +517,6 @@
     // settings
     private QSPanelController mQSPanelController;
 
-    private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     KeyguardIndicationController mKeyguardIndicationController;
 
     private View mReportRejectedTouch;
@@ -542,12 +536,11 @@
     private final BrightnessSliderController.Factory mBrightnessSliderFactory;
     private final FeatureFlags mFeatureFlags;
     private final FragmentService mFragmentService;
+    private final ScreenOffAnimationController mScreenOffAnimationController;
     private final WallpaperController mWallpaperController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final MessageRouter mMessageRouter;
     private final WallpaperManager mWallpaperManager;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    private final TunerService mTunerService;
 
     private StatusBarComponent mStatusBarComponent;
 
@@ -752,11 +745,9 @@
             DozeScrimController dozeScrimController,
             VolumeComponent volumeComponent,
             CommandQueue commandQueue,
-            CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             StatusBarComponent.Factory statusBarComponentFactory,
             PluginManager pluginManager,
             Optional<LegacySplitScreen> splitScreenOptional,
-            LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
             ShadeController shadeController,
@@ -768,7 +759,6 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            OperatorNameViewController.Factory operatorNameViewControllerFactory,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DemoModeController demoModeController,
@@ -776,11 +766,9 @@
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
             BrightnessSliderController.Factory brightnessSliderFactory,
+            ScreenOffAnimationController screenOffAnimationController,
             WallpaperController wallpaperController,
             OngoingCallController ongoingCallController,
-            SystemStatusAnimationScheduler animationScheduler,
-            StatusBarLocationPublisher locationPublisher,
-            StatusBarIconController statusBarIconController,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             FeatureFlags featureFlags,
@@ -789,10 +777,7 @@
             @Main DelayableExecutor delayableExecutor,
             @Main MessageRouter messageRouter,
             WallpaperManager wallpaperManager,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             Optional<StartingSurface> startingSurfaceOptional,
-            TunerService tunerService,
-            DumpManager dumpManager,
             ActivityLaunchAnimator activityLaunchAnimator,
             NotifPipelineFlags notifPipelineFlags) {
         super(context);
@@ -807,7 +792,6 @@
         mKeyguardBypassController = keyguardBypassController;
         mKeyguardStateController = keyguardStateController;
         mHeadsUpManager = headsUpManagerPhone;
-        mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
         mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mDynamicPrivacyController = dynamicPrivacyController;
@@ -857,13 +841,11 @@
         mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
         mVolumeComponent = volumeComponent;
         mCommandQueue = commandQueue;
-        mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger;
         mStatusBarComponentFactory = statusBarComponentFactory;
         mPluginManager = pluginManager;
         mSplitScreenOptional = splitScreenOptional;
         mStatusBarNotificationActivityStarterBuilder = statusBarNotificationActivityStarterBuilder;
         mShadeController = shadeController;
-        mLightsOutNotifController =  lightsOutNotifController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
@@ -877,10 +859,7 @@
         mBrightnessSliderFactory = brightnessSliderFactory;
         mWallpaperController = wallpaperController;
         mOngoingCallController = ongoingCallController;
-        mAnimationScheduler = animationScheduler;
         mStatusBarSignalPolicy = statusBarSignalPolicy;
-        mStatusBarLocationPublisher = locationPublisher;
-        mStatusBarIconController = statusBarIconController;
         mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
         mFeatureFlags = featureFlags;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
@@ -888,14 +867,14 @@
         mMainExecutor = delayableExecutor;
         mMessageRouter = messageRouter;
         mWallpaperManager = wallpaperManager;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mTunerService = tunerService;
 
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
         mStartingSurfaceOptional = startingSurfaceOptional;
         mNotifPipelineFlags = notifPipelineFlags;
         lockscreenShadeTransitionController.setStatusbar(this);
 
+        mScreenOffAnimationController = screenOffAnimationController;
+
         mPanelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged);
 
         mBubbleExpandListener =
@@ -1146,19 +1125,15 @@
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
         mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
         mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
-        mStatusBarWindowController.getFragmentHostManager()
-                .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
-                    StatusBarFragmentComponent statusBarFragmentComponent =
-                            ((CollapsedStatusBarFragment) fragment).getStatusBarFragmentComponent();
-                    if (statusBarFragmentComponent == null) {
-                        throw new IllegalStateException(
-                                "CollapsedStatusBarFragment should have a valid component");
-                    }
 
-                    mStatusBarView = statusBarFragmentComponent.getPhoneStatusBarView();
-                    mPhoneStatusBarViewController =
-                            statusBarFragmentComponent.getPhoneStatusBarViewController();
-
+        // Set up CollapsedStatusBarFragment and PhoneStatusBarView
+        StatusBarInitializer initializer = mStatusBarComponent.getStatusBarInitializer();
+        initializer.setStatusBarViewUpdatedListener(
+                (statusBarView, statusBarViewController, statusBarTransitions) -> {
+                    mStatusBarView = statusBarView;
+                    mPhoneStatusBarViewController = statusBarViewController;
+                    mStatusBarTransitions = statusBarTransitions;
+                    mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
                     // Ensure we re-propagate panel expansion values to the panel controller and
                     // any listeners it may have, such as PanelBar. This will also ensure we
                     // re-display the notification panel if necessary (for example, if
@@ -1166,17 +1141,9 @@
                     // displayed).
                     mNotificationPanelViewController.updatePanelExpansionAndVisibility();
                     setBouncerShowingForStatusBarComponents(mBouncerShowing);
-
-                    mLightsOutNotifController.setLightsOutNotifView(
-                            mStatusBarView.findViewById(R.id.notification_lights_out));
-                    mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
                     checkBarModes();
-                }).getFragmentManager()
-                .beginTransaction()
-                .replace(R.id.status_bar_container,
-                        mStatusBarComponent.createCollapsedStatusBarFragment(),
-                        CollapsedStatusBarFragment.TAG)
-                .commit();
+                });
+        initializer.initializeStatusBar(mStatusBarComponent);
 
         mHeadsUpManager.setup(mVisualStabilityManager);
         mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
@@ -1233,6 +1200,8 @@
             Runnable updateOpaqueness = () -> {
                 mNotificationShadeWindowController.setLightRevealScrimOpaque(
                         mLightRevealScrim.isScrimOpaque());
+                mScreenOffAnimationController
+                        .onScrimOpaqueChanged(mLightRevealScrim.isScrimOpaque());
             };
             if (opaque) {
                 // Delay making the view opaque for a frame, because it needs some time to render
@@ -1242,7 +1211,8 @@
                 updateOpaqueness.run();
             }
         });
-        mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
+
+        mScreenOffAnimationController.initialize(this, mLightRevealScrim);
         updateLightRevealScrimVisibility();
 
         mNotificationPanelViewController.initDependencies(
@@ -1493,7 +1463,7 @@
      * @param why the reason for the wake up
      */
     public void wakeUpIfDozing(long time, View where, String why) {
-        if (mDozing && !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()) {
+        if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
             mPowerManager.wakeUp(
                     time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
             mWakeUpComingFromTouch = true;
@@ -1550,10 +1520,8 @@
 
         mHeadsUpManager.addListener(mStatusBarComponent.getStatusBarHeadsUpChangeListener());
 
-        mHeadsUpManager.addListener(mStatusBarComponent.getStatusBarHeadsUpChangeListener());
-
         // Listen for demo mode changes
-        mDemoModeController.addCallback(mStatusBarComponent.getStatusBarDemoMode());
+        mDemoModeController.addCallback(mDemoModeCallback);
 
         if (mCommandQueueCallbacks != null) {
             mCommandQueue.removeCallback(mCommandQueueCallbacks);
@@ -1613,10 +1581,6 @@
         Trace.endSection();
     }
 
-    protected PhoneStatusBarView getStatusBarView() {
-        return mStatusBarView;
-    }
-
     public NotificationShadeWindowView getNotificationShadeWindowView() {
         return mNotificationShadeWindowView;
     }
@@ -2227,14 +2191,10 @@
                 }, false, sUiEventLogger).show(animationDelay);
     }
 
-    protected BarTransitions getStatusBarTransitions() {
-        return mNotificationShadeWindowViewController.getBarTransitions();
-    }
-
     public void checkBarModes() {
         if (mDemoModeController.isInDemoMode()) return;
-        if (mNotificationShadeWindowViewController != null && getStatusBarTransitions() != null) {
-            checkBarMode(mStatusBarMode, mStatusBarWindowState, getStatusBarTransitions());
+        if (mStatusBarTransitions != null) {
+            checkBarMode(mStatusBarMode, mStatusBarWindowState, mStatusBarTransitions);
         }
         mNavigationBarController.checkNavBarModes(mDisplayId);
         mNoAnimationOnNextBarModeChange = false;
@@ -2261,9 +2221,8 @@
     }
 
     private void finishBarAnimations() {
-        if (mNotificationShadeWindowController != null
-                && mNotificationShadeWindowViewController.getBarTransitions() != null) {
-            mNotificationShadeWindowViewController.getBarTransitions().finishAnimations();
+        if (mStatusBarTransitions != null) {
+            mStatusBarTransitions.finishAnimations();
         }
         mNavigationBarController.finishBarAnimations(mDisplayId);
     }
@@ -2317,8 +2276,7 @@
         pw.println("  ShadeWindowView: ");
         if (mNotificationShadeWindowViewController != null) {
             mNotificationShadeWindowViewController.dump(fd, pw, args);
-            dumpBarTransitions(pw, "PhoneStatusBarTransitions",
-                    mNotificationShadeWindowViewController.getBarTransitions());
+            dumpBarTransitions(pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
         }
 
         pw.println("  mMediaManager: ");
@@ -2750,9 +2708,6 @@
             mStatusBarWindowController.refreshStatusBarHeight();
         }
 
-        if (mStatusBarView != null) {
-            mStatusBarView.updateResources();
-        }
         if (mNotificationPanelViewController != null) {
             mNotificationPanelViewController.updateResources();
         }
@@ -2945,7 +2900,7 @@
             updatePanelExpansionForKeyguard();
         }
         if (shouldBeKeyguard) {
-            if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
+            if (mScreenOffAnimationController.isKeyguardShowDelayed()
                     || (isGoingToSleep()
                     && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF)) {
                 // Delay showing the keyguard until screen turned off.
@@ -2953,7 +2908,17 @@
                 showKeyguardImpl();
             }
         } else {
-            return hideKeyguardImpl(force);
+            // During folding a foldable device this might be called as a result of
+            // 'onScreenTurnedOff' call for the inner display.
+            // In this case:
+            //  * When phone is locked on folding: it doesn't make sense to hide keyguard as it
+            //    will be immediately locked again
+            //  * When phone is unlocked: we still don't want to execute hiding of the keyguard
+            //    as the animation could prepare 'fake AOD' interface (without actually
+            //    transitioning to keyguard state) and this might reset the view states
+            if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
+                return hideKeyguardImpl(force);
+            }
         }
         return false;
     }
@@ -3116,6 +3081,7 @@
         mNotificationPanelViewController.onAffordanceLaunchEnded();
         mNotificationPanelViewController.cancelAnimation();
         mNotificationPanelViewController.setAlpha(1f);
+        mNotificationPanelViewController.resetTranslation();
         mNotificationPanelViewController.resetViewGroupFade();
         updateDozingState();
         updateScrimController();
@@ -3191,7 +3157,7 @@
         // If we're dozing and we'll be animating the screen off, the keyguard isn't currently
         // visible but will be shortly for the animation, so we should proceed as if it's visible.
         boolean visibleNotOccludedOrWillBe =
-                visibleNotOccluded || (mDozing && mDozeParameters.shouldControlUnlockedScreenOff());
+                visibleNotOccluded || (mDozing && mDozeParameters.shouldDelayKeyguardShow());
 
         boolean wakeAndUnlock = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -3546,9 +3512,9 @@
             mBypassHeadsUpNotifier.setFullyAwake(false);
             mKeyguardBypassController.onStartedGoingToSleep();
 
-            // The screen off animation uses our LightRevealScrim - we need to be expanded for it to
-            // be visible.
-            if (mDozeParameters.shouldControlUnlockedScreenOff()) {
+            // 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);
             }
 
@@ -3576,7 +3542,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 (mUnlockedScreenOffAnimationController.isScreenOffLightRevealAnimationPlaying()) {
+            if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
                 makeExpandedInvisible();
             }
 
@@ -3795,7 +3761,7 @@
     public boolean shouldIgnoreTouch() {
         return (mStatusBarStateController.isDozing()
                 && mDozeServiceHost.getIgnoreTouchWhilePulsing())
-                || mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
+                || mScreenOffAnimationController.shouldIgnoreKeyguardTouches();
     }
 
     // Begin Extra BaseStatusBar methods.
@@ -4227,7 +4193,7 @@
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
-                if (!mUserSetup && mStatusBarView != null) {
+                if (!mUserSetup) {
                     animateCollapseQuickSettings();
                 }
                 if (mNotificationPanelViewController != null) {
@@ -4342,7 +4308,7 @@
                     updateTheme();
                     mNavigationBarController.touchAutoDim(mDisplayId);
                     Trace.beginSection("StatusBar#updateKeyguardState");
-                    if (mState == StatusBarState.KEYGUARD && mStatusBarView != null) {
+                    if (mState == StatusBarState.KEYGUARD) {
                         mNotificationPanelViewController.cancelPendingPanelCollapse();
                     }
                     updateDozingState();
@@ -4434,4 +4400,14 @@
                     return mStartingSurfaceOptional.get().getBackgroundColor(task);
                 }
             };
+
+    private final DemoMode mDemoModeCallback = new DemoMode() {
+        @Override
+        public void onDemoModeFinished() {
+            checkBarModes();
+        }
+
+        @Override
+        public void dispatchDemoCommand(String command, Bundle args) { }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index 05b4776..b391de3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -529,10 +529,8 @@
             if (StatusBar.DEBUG_WINDOW_STATE) {
                 Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state));
             }
-            if (mStatusBar.getStatusBarView() != null
-                    && !showing
-                    && mStatusBarStateController.getState() == StatusBarState.SHADE) {
-                    mNotificationPanelViewController.collapsePanel(
+            if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+                mNotificationPanelViewController.collapsePanel(
                             false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
index e642b2e..29c1372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
@@ -21,48 +21,70 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
+import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_VIEW;
 
 import android.annotation.NonNull;
 import android.os.Bundle;
 import android.view.View;
 
-import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.navigationbar.NavigationBarController;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
+import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
-/** */
-@StatusBarComponent.StatusBarScope
-public class StatusBarDemoMode implements DemoMode {
-    private final StatusBar mStatusBar;
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
-    private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+/**
+ * A controller that updates status-bar-related views during demo mode.
+ *
+ * This class extends ViewController not because it controls a specific view, but because we want it
+ * to get torn down and re-created in line with the view's lifecycle.
+ */
+@StatusBarFragmentScope
+public class StatusBarDemoMode extends ViewController<View> implements DemoMode {
+    private final Clock mClockView;
+    private final View mOperatorNameView;
+    private final DemoModeController mDemoModeController;
+    private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private final NavigationBarController mNavigationBarController;
     private final int mDisplayId;
 
     @Inject
     StatusBarDemoMode(
-            StatusBar statusBar,
-            NotificationShadeWindowController notificationShadeWindowController,
-            NotificationShadeWindowViewController notificationShadeWindowViewController,
+            Clock clockView,
+            @Named(OPERATOR_NAME_VIEW) View operatorNameView,
+            DemoModeController demoModeController,
+            PhoneStatusBarTransitions phoneStatusBarTransitions,
             NavigationBarController navigationBarController,
             @DisplayId int displayId) {
-        mStatusBar = statusBar;
-        mNotificationShadeWindowController = notificationShadeWindowController;
-        mNotificationShadeWindowViewController = notificationShadeWindowViewController;
+        super(clockView);
+        mClockView = clockView;
+        mOperatorNameView = operatorNameView;
+        mDemoModeController = demoModeController;
+        mPhoneStatusBarTransitions = phoneStatusBarTransitions;
         mNavigationBarController = navigationBarController;
         mDisplayId = displayId;
     }
 
     @Override
+    protected void onViewAttached() {
+        mDemoModeController.addCallback(this);
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mDemoModeController.removeCallback(this);
+    }
+
+    @Override
     public List<String> demoCommands() {
         List<String> s = new ArrayList<>();
         s.add(DemoMode.COMMAND_BARS);
@@ -74,21 +96,23 @@
     @Override
     public void onDemoModeStarted() {
         // Must send this message to any view that we delegate to via dispatchDemoCommandToView
-        dispatchDemoModeStartedToView(R.id.clock);
-        dispatchDemoModeStartedToView(R.id.operator_name);
+        dispatchDemoModeStartedToView(mClockView);
+        dispatchDemoModeStartedToView(mOperatorNameView);
     }
 
     @Override
     public void onDemoModeFinished() {
-        dispatchDemoModeFinishedToView(R.id.clock);
-        dispatchDemoModeFinishedToView(R.id.operator_name);
-        mStatusBar.checkBarModes();
+        dispatchDemoModeFinishedToView(mClockView);
+        dispatchDemoModeFinishedToView(mOperatorNameView);
     }
 
     @Override
     public void dispatchDemoCommand(String command, @NonNull Bundle args) {
         if (command.equals(COMMAND_CLOCK)) {
-            dispatchDemoCommandToView(command, args, R.id.clock);
+            dispatchDemoCommandToView(command, args, mClockView);
+        }
+        if (command.equals(COMMAND_OPERATOR)) {
+            dispatchDemoCommandToView(command, args, mOperatorNameView);
         }
         if (command.equals(COMMAND_BARS)) {
             String mode = args.getString("mode");
@@ -100,42 +124,25 @@
                                                     -1;
             if (barMode != -1) {
                 boolean animate = true;
-                if (mNotificationShadeWindowController != null
-                        && mNotificationShadeWindowViewController.getBarTransitions() != null) {
-                    mNotificationShadeWindowViewController.getBarTransitions().transitionTo(
-                            barMode, animate);
-                }
+                mPhoneStatusBarTransitions.transitionTo(barMode, animate);
                 mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
             }
         }
-        if (command.equals(COMMAND_OPERATOR)) {
-            dispatchDemoCommandToView(command, args, R.id.operator_name);
-        }
     }
 
-    private void dispatchDemoModeStartedToView(int id) {
-        View statusBarView = mStatusBar.getStatusBarView();
-        if (statusBarView == null) return;
-        View v = statusBarView.findViewById(id);
+    private void dispatchDemoModeStartedToView(View v) {
         if (v instanceof DemoModeCommandReceiver) {
             ((DemoModeCommandReceiver) v).onDemoModeStarted();
         }
     }
 
-    //TODO: these should have controllers, and this method should be removed
-    private void dispatchDemoCommandToView(String command, Bundle args, int id) {
-        View statusBarView = mStatusBar.getStatusBarView();
-        if (statusBarView == null) return;
-        View v = statusBarView.findViewById(id);
+    private void dispatchDemoCommandToView(String command, Bundle args, View v) {
         if (v instanceof DemoModeCommandReceiver) {
             ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
         }
     }
 
-    private void dispatchDemoModeFinishedToView(int id) {
-        View statusBarView = mStatusBar.getStatusBarView();
-        if (statusBarView == null) return;
-        View v = statusBarView.findViewById(id);
+    private void dispatchDemoModeFinishedToView(View v) {
         if (v instanceof DemoModeCommandReceiver) {
             ((DemoModeCommandReceiver) v).onDemoModeFinished();
         }
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 b84e6e6..875b7e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -111,8 +111,6 @@
     private final NavigationModeController mNavigationModeController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final KeyguardBouncer.Factory mKeyguardBouncerFactory;
-    private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private final KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     private KeyguardMessageAreaController mKeyguardMessageAreaController;
     private final Lazy<ShadeController> mShadeController;
@@ -244,8 +242,6 @@
             KeyguardStateController keyguardStateController,
             NotificationMediaManager notificationMediaManager,
             KeyguardBouncer.Factory keyguardBouncerFactory,
-            WakefulnessLifecycle wakefulnessLifecycle,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
             Lazy<ShadeController> shadeController) {
         mContext = context;
@@ -260,8 +256,6 @@
         mStatusBarStateController = sysuiStatusBarStateController;
         mDockManager = dockManager;
         mKeyguardBouncerFactory = keyguardBouncerFactory;
-        mWakefulnessLifecycle = wakefulnessLifecycle;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
         mShadeController = shadeController;
     }
@@ -416,7 +410,7 @@
      * dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
      */
     public void showGenericBouncer(boolean scrimmed) {
-        if (mAlternateAuthInterceptor != null) {
+        if (shouldShowAltAuth()) {
             updateAlternateAuthShowing(mAlternateAuthInterceptor.showAlternateAuthBouncer());
             return;
         }
@@ -424,6 +418,11 @@
         showBouncer(scrimmed);
     }
 
+    private boolean shouldShowAltAuth() {
+        return mAlternateAuthInterceptor != null
+                && mKeyguardUpdateManager.isUnlockingWithBiometricAllowed(true);
+    }
+
     /**
      * Hides the input bouncer (pin/password/pattern).
      */
@@ -479,7 +478,7 @@
 
             // If there is an an alternate auth interceptor (like the UDFPS), show that one instead
             // of the bouncer.
-            if (mAlternateAuthInterceptor != null) {
+            if (shouldShowAltAuth()) {
                 if (!afterKeyguardGone) {
                     mBouncer.setDismissAction(mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
                     mAfterKeyguardGoneAction = null;
@@ -1155,7 +1154,7 @@
 
     @Override
     public ViewRootImpl getViewRootImpl() {
-        return mStatusBar.getStatusBarView().getViewRootImpl();
+        return mNotificationShadeWindowController.getNotificationShadeView().getViewRootImpl();
     }
 
     public void launchPendingWakeupAction() {
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 32aae6c..2ba37c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -23,7 +23,8 @@
         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
         statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
         if (!isExpandingFullyAbove) {
-            statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt())
+            statusBar.collapsePanelWithDuration(
+                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 863ce57..ff86d74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -660,14 +660,6 @@
 
     // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
 
-    private int getVisibleNotificationsCount() {
-        if (mNotifPipelineFlags.isNewPipelineEnabled()) {
-            return mNotifPipeline.getShadeListCount();
-        } else {
-            return mEntryManager.getActiveNotificationsCount();
-        }
-    }
-
     /**
      * Public builder for {@link StatusBarNotificationActivityStarter}.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 43264b6..e3b4caa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -45,6 +45,10 @@
 
 /**
  * Base class for dialogs that should appear over panels and keyguard.
+ *
+ * Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to
+ * listeners on whether this dialog is showing.
+ *
  * The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast,
  * and dismisses itself when it receives the broadcast.
  */
@@ -54,8 +58,9 @@
             "persist.systemui.flag_tablet_dialog_width";
 
     private final Context mContext;
-    private final DismissReceiver mDismissReceiver;
+    @Nullable private final DismissReceiver mDismissReceiver;
     private final Handler mHandler = new Handler();
+    @Nullable private final SystemUIDialogManager mDialogManager;
 
     private int mLastWidth = Integer.MIN_VALUE;
     private int mLastHeight = Integer.MIN_VALUE;
@@ -66,11 +71,27 @@
         this(context, R.style.Theme_SystemUI_Dialog);
     }
 
+    public SystemUIDialog(Context context, SystemUIDialogManager dialogManager) {
+        this(context, R.style.Theme_SystemUI_Dialog, true, dialogManager);
+    }
+
     public SystemUIDialog(Context context, int theme) {
         this(context, theme, true /* dismissOnDeviceLock */);
     }
 
+    public SystemUIDialog(Context context, int theme, SystemUIDialogManager dialogManager) {
+        this(context, theme, true /* dismissOnDeviceLock */, dialogManager);
+    }
+
     public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
+        this(context, theme, dismissOnDeviceLock, null);
+    }
+
+    /**
+     * @param udfpsDialogManager If set, UDFPS will hide if this dialog is showing.
+     */
+    public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+            SystemUIDialogManager dialogManager) {
         super(context, theme);
         mContext = context;
 
@@ -80,6 +101,7 @@
         getWindow().setAttributes(attrs);
 
         mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null;
+        mDialogManager = dialogManager;
     }
 
     @Override
@@ -126,30 +148,7 @@
      * the device configuration changes, and the result will be used to resize this dialog window.
      */
     protected int getWidth() {
-        boolean isOnTablet =
-                mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
-        if (!isOnTablet) {
-            return ViewGroup.LayoutParams.MATCH_PARENT;
-        }
-
-        int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
-        if (flagValue == -1) {
-            // The width of bottom sheets (624dp).
-            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
-                    mContext.getResources().getDisplayMetrics()));
-        } else if (flagValue == -2) {
-            // The suggested small width for all dialogs (348dp)
-            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
-                    mContext.getResources().getDisplayMetrics()));
-        } else if (flagValue > 0) {
-            // Any given width.
-            return Math.round(
-                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
-                            mContext.getResources().getDisplayMetrics()));
-        } else {
-            // By default we use the same width as the notification shade in portrait mode (504dp).
-            return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
-        }
+        return getDefaultDialogWidth(mContext);
     }
 
     /**
@@ -157,7 +156,7 @@
      * the device configuration changes, and the result will be used to resize this dialog window.
      */
     protected int getHeight() {
-        return ViewGroup.LayoutParams.WRAP_CONTENT;
+        return getDefaultDialogHeight();
     }
 
     @Override
@@ -168,6 +167,10 @@
             mDismissReceiver.register();
         }
 
+        if (mDialogManager != null) {
+            mDialogManager.setShowing(this, true);
+        }
+
         // Listen for configuration changes to resize this dialog window. This is mostly necessary
         // for foldables that often go from large <=> small screen when folding/unfolding.
         ViewRootImpl.addConfigCallback(this);
@@ -181,6 +184,10 @@
             mDismissReceiver.unregister();
         }
 
+        if (mDialogManager != null) {
+            mDialogManager.setShowing(this, false);
+        }
+
         ViewRootImpl.removeConfigCallback(this);
     }
 
@@ -267,6 +274,45 @@
         dismissReceiver.register();
     }
 
+    /** Set an appropriate size to {@code dialog} depending on the current configuration. */
+    public static void setDialogSize(Dialog dialog) {
+        // We need to create the dialog first, otherwise the size will be overridden when it is
+        // created.
+        dialog.create();
+        dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
+                getDefaultDialogHeight());
+    }
+
+    private static int getDefaultDialogWidth(Context context) {
+        boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+        if (!isOnTablet) {
+            return ViewGroup.LayoutParams.MATCH_PARENT;
+        }
+
+        int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
+        if (flagValue == -1) {
+            // The width of bottom sheets (624dp).
+            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
+                    context.getResources().getDisplayMetrics()));
+        } else if (flagValue == -2) {
+            // The suggested small width for all dialogs (348dp)
+            return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
+                    context.getResources().getDisplayMetrics()));
+        } else if (flagValue > 0) {
+            // Any given width.
+            return Math.round(
+                    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
+                            context.getResources().getDisplayMetrics()));
+        } else {
+            // By default we use the same width as the notification shade in portrait mode (504dp).
+            return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+        }
+    }
+
+    private static int getDefaultDialogHeight() {
+        return ViewGroup.LayoutParams.WRAP_CONTENT;
+    }
+
     private static class DismissReceiver extends BroadcastReceiver {
         private static final IntentFilter INTENT_FILTER = new IntentFilter();
         static {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
new file mode 100644
index 0000000..204f710
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManager.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+/**
+ * Register dialogs to this manager if extraneous affordances (like the UDFPS sensor area)
+ * should be hidden from the screen when the dialog shows.
+ *
+ * Currently, only used if UDFPS is supported on the device; however, can be extended in the future
+ * for other use cases.
+ */
+@SysUISingleton
+public class SystemUIDialogManager implements Dumpable {
+    private final StatusBarKeyguardViewManager mKeyguardViewManager;
+
+    private final Set<SystemUIDialog> mDialogsShowing = new HashSet<>();
+    private final Set<Listener> mListeners = new HashSet<>();
+
+    @Inject
+    public SystemUIDialogManager(
+            DumpManager dumpManager,
+            StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+        dumpManager.registerDumpable(this);
+        mKeyguardViewManager = statusBarKeyguardViewManager;
+    }
+
+    /**
+     * Whether listeners should hide affordances like the UDFPS sensor icon.
+     */
+    public boolean shouldHideAffordance() {
+        return !mDialogsShowing.isEmpty();
+    }
+
+    /**
+     * Register a listener to receive callbacks.
+     */
+    public void registerListener(@NonNull Listener listener) {
+        mListeners.add(listener);
+    }
+
+    /**
+     * Unregister a listener from receiving callbacks.
+     */
+    public void unregisterListener(@NonNull Listener listener) {
+        mListeners.remove(listener);
+    }
+
+    void setShowing(SystemUIDialog dialog, boolean showing) {
+        final boolean wasHidingAffordances = shouldHideAffordance();
+        if (showing) {
+            mDialogsShowing.add(dialog);
+        } else {
+            mDialogsShowing.remove(dialog);
+        }
+
+        if (wasHidingAffordances != shouldHideAffordance()) {
+            updateDialogListeners();
+        }
+    }
+
+    private void updateDialogListeners() {
+        if (shouldHideAffordance()) {
+            mKeyguardViewManager.resetAlternateAuth(true);
+        }
+
+        for (Listener listener : mListeners) {
+            listener.shouldHideAffordances(shouldHideAffordance());
+        }
+    }
+
+    @Override
+    public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("listeners:");
+        for (Listener listener : mListeners) {
+            pw.println("\t" + listener);
+        }
+        pw.println("dialogs tracked:");
+        for (SystemUIDialog dialog : mDialogsShowing) {
+            pw.println("\t" + dialog);
+        }
+    }
+
+    /** SystemUIDialogManagerListener */
+    public interface Listener {
+        /**
+         * Callback where shouldHide=true if listeners should hide their views that may overlap
+         * a showing dialog.
+         */
+        void shouldHideAffordances(boolean shouldHide);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index cddde64..fc661b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -51,7 +51,7 @@
     private val keyguardStateController: KeyguardStateController,
     private val dozeParameters: dagger.Lazy<DozeParameters>,
     private val globalSettings: GlobalSettings
-) : WakefulnessLifecycle.Observer {
+) : WakefulnessLifecycle.Observer, ScreenOffAnimation {
     private val handler = Handler()
 
     private lateinit var statusBar: StatusBar
@@ -98,7 +98,7 @@
         }
     }
 
-    fun initialize(
+    override fun initialize(
         statusBar: StatusBar,
         lightRevealScrim: LightRevealScrim
     ) {
@@ -122,7 +122,7 @@
      * Animates in the provided keyguard view, ending in the same position that it will be in on
      * AOD.
      */
-    fun animateInKeyguard(keyguardView: View, after: Runnable) {
+    override fun animateInKeyguard(keyguardView: View, after: Runnable) {
         shouldAnimateInKeyguard = false
         keyguardView.alpha = 0f
         keyguardView.visibility = View.VISIBLE
@@ -197,8 +197,8 @@
         }
     }
 
-    override fun onStartedGoingToSleep() {
-        if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
+    override fun startAnimation(): Boolean {
+        if (shouldPlayUnlockedScreenOffAnimation()) {
             decidedToAnimateGoingToSleep = true
 
             shouldAnimateInKeyguard = true
@@ -210,8 +210,11 @@
                 // Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
                 statusBar.notificationPanelViewController.showAodUi()
             }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
+
+            return true
         } else {
             decidedToAnimateGoingToSleep = false
+            return false
         }
     }
 
@@ -244,8 +247,12 @@
         // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's
         // already expanded and showing notifications/QS, the animation looks really messy. For now,
         // disable it if the notification panel is not fully collapsed.
-        if (!this::statusBar.isInitialized ||
-                !statusBar.notificationPanelViewController.isFullyCollapsed) {
+        if ((!this::statusBar.isInitialized ||
+                !statusBar.notificationPanelViewController.isFullyCollapsed)
+                // Status bar might be expanded because we have started
+                // playing the animation already
+                && !isAnimationPlaying()
+        ) {
             return false
         }
 
@@ -269,23 +276,37 @@
         callbacks.remove(callback)
     }
 
-    fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) {
+    private fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) {
         callbacks.forEach {
             it.onUnlockedScreenOffProgressUpdate(linear, eased)
         }
     }
 
-/**
+    /**
      * Whether we're doing the light reveal animation or we're done with that and animating in the
      * AOD UI.
      */
-    fun isScreenOffAnimationPlaying(): Boolean {
+    override fun isAnimationPlaying(): Boolean {
         return lightRevealAnimationPlaying || aodUiAnimationPlaying
     }
 
-    fun shouldAnimateInKeyguard(): Boolean {
-        return shouldAnimateInKeyguard
-    }
+    override fun shouldAnimateInKeyguard(): Boolean =
+        shouldAnimateInKeyguard
+
+    override fun shouldHideScrimOnWakeUp(): Boolean =
+        isScreenOffLightRevealAnimationPlaying()
+
+    override fun overrideNotificationsDozeAmount(): Boolean =
+        shouldPlayUnlockedScreenOffAnimation() && isAnimationPlaying()
+
+    override fun shouldShowAodIconsWhenShade(): Boolean =
+        isAnimationPlaying()
+
+    override fun shouldAnimateAodIcons(): Boolean =
+        shouldPlayUnlockedScreenOffAnimation()
+
+    override fun shouldPlayAnimation(): Boolean =
+        shouldPlayUnlockedScreenOffAnimation()
 
     /**
      * Whether the light reveal animation is playing. The second part of the screen off animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 375641f..61dba92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -16,24 +16,27 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
+import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.STATUS_BAR_FRAGMENT;
+
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 import com.android.keyguard.LockIconViewController;
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.statusbar.NotificationShelfController;
+import com.android.systemui.statusbar.core.StatusBarInitializer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
 import com.android.systemui.statusbar.phone.SplitShadeHeaderController;
 import com.android.systemui.statusbar.phone.StatusBarCommandQueueCallbacks;
-import com.android.systemui.statusbar.phone.StatusBarDemoMode;
 import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
+import javax.inject.Named;
 import javax.inject.Scope;
 
 import dagger.Subcomponent;
@@ -106,12 +109,6 @@
     AuthRippleController getAuthRippleController();
 
     /**
-     * Creates a StatusBarDemoMode.
-     */
-    @StatusBarScope
-    StatusBarDemoMode getStatusBarDemoMode();
-
-    /**
      * Creates a StatusBarHeadsUpChangeListener.
      */
     @StatusBarScope
@@ -133,5 +130,12 @@
      * Creates a new {@link CollapsedStatusBarFragment} each time it's called. See
      * {@link StatusBarViewModule#createCollapsedStatusBarFragment}.
      */
+    @Named(STATUS_BAR_FRAGMENT)
     CollapsedStatusBarFragment createCollapsedStatusBarFragment();
+
+    /**
+     * Creates a StatusBarInitializer
+     */
+    @StatusBarScope
+    StatusBarInitializer getStatusBarInitializer();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 33171b2..f93a8dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -38,7 +38,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -60,11 +59,9 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
-import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -85,23 +82,19 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.statusbar.phone.LightsOutNotifController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy;
 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -112,7 +105,6 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.MessageRouter;
@@ -201,11 +193,9 @@
             DozeScrimController dozeScrimController,
             VolumeComponent volumeComponent,
             CommandQueue commandQueue,
-            CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
             StatusBarComponent.Factory statusBarComponentFactory,
             PluginManager pluginManager,
             Optional<LegacySplitScreen> splitScreenOptional,
-            LightsOutNotifController lightsOutNotifController,
             StatusBarNotificationActivityStarter.Builder
                     statusBarNotificationActivityStarterBuilder,
             ShadeController shadeController,
@@ -217,7 +207,6 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            OperatorNameViewController.Factory operatorNameViewControllerFactory,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DemoModeController demoModeController,
@@ -225,11 +214,9 @@
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             NotificationIconAreaController notificationIconAreaController,
             BrightnessSliderController.Factory brightnessSliderFactory,
+            ScreenOffAnimationController screenOffAnimationController,
             WallpaperController wallpaperController,
             OngoingCallController ongoingCallController,
-            SystemStatusAnimationScheduler animationScheduler,
-            StatusBarLocationPublisher locationPublisher,
-            StatusBarIconController statusBarIconController,
             StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
             LockscreenShadeTransitionController transitionController,
             FeatureFlags featureFlags,
@@ -238,10 +225,7 @@
             @Main DelayableExecutor delayableExecutor,
             @Main MessageRouter messageRouter,
             WallpaperManager wallpaperManager,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             Optional<StartingSurface> startingSurfaceOptional,
-            TunerService tunerService,
-            DumpManager dumpManager,
             ActivityLaunchAnimator activityLaunchAnimator,
             NotifPipelineFlags notifPipelineFlags) {
         return new StatusBar(
@@ -304,11 +288,9 @@
                 dozeScrimController,
                 volumeComponent,
                 commandQueue,
-                collapsedStatusBarFragmentLogger,
                 statusBarComponentFactory,
                 pluginManager,
                 splitScreenOptional,
-                lightsOutNotifController,
                 statusBarNotificationActivityStarterBuilder,
                 shadeController,
                 statusBarKeyguardViewManager,
@@ -319,7 +301,6 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
-                operatorNameViewControllerFactory,
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 demoModeController,
@@ -327,11 +308,9 @@
                 statusBarTouchableRegionManager,
                 notificationIconAreaController,
                 brightnessSliderFactory,
+                screenOffAnimationController,
                 wallpaperController,
                 ongoingCallController,
-                animationScheduler,
-                locationPublisher,
-                statusBarIconController,
                 statusBarHideIconsForBouncerManager,
                 transitionController,
                 featureFlags,
@@ -340,10 +319,7 @@
                 delayableExecutor,
                 messageRouter,
                 wallpaperManager,
-                unlockedScreenOffAnimationController,
                 startingSurfaceOptional,
-                tunerService,
-                dumpManager,
                 activityLaunchAnimator,
                 notifPipelineFlags
         );
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 cb140adf..ebd58fb 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
@@ -73,6 +73,7 @@
     public static final String SPLIT_SHADE_HEADER = "split_shade_header";
     private static final String SPLIT_SHADE_BATTERY_VIEW = "split_shade_battery_view";
     public static final String SPLIT_SHADE_BATTERY_CONTROLLER = "split_shade_battery_controller";
+    public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment";
 
     /** */
     @Provides
@@ -238,10 +239,11 @@
      * time this method is called. This is intentional because we need fragments to re-created in
      * certain lifecycle scenarios.
      *
-     * **IMPORTANT**: This method also intentionally does not have a {@link Provides} annotation. If
-     * you need to get access to a {@link CollapsedStatusBarFragment}, go through
-     * {@link StatusBarFragmentComponent} instead.
+     * This provider is {@link Named} such that it does not conflict with the provider inside of
+     * {@link StatusBarFragmentComponent}.
      */
+    @Provides
+    @Named(STATUS_BAR_FRAGMENT)
     public static CollapsedStatusBarFragment createCollapsedStatusBarFragment(
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
             OngoingCallController ongoingCallController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index d6ba6f3..051fbaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -28,6 +28,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.Fragment;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -66,18 +67,15 @@
 import com.android.systemui.statusbar.policy.EncryptionHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import org.jetbrains.annotations.NotNull;
-
 import java.util.ArrayList;
 import java.util.List;
 
-import javax.inject.Inject;
-
 /**
  * Contains the collapsed status bar and handles hiding/showing based on disable flags
  * and keyguard state. Also manages lifecycle to make sure the views it contains are being
  * updated by the StatusBarIconController and DarkIconManager while it is attached.
  */
+@SuppressLint("ValidFragment")
 public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
         StatusBarStateController.StateListener,
         SystemStatusAnimationCallback {
@@ -131,7 +129,7 @@
     };
     private OperatorNameViewController mOperatorNameViewController;
 
-    @Inject
+    @SuppressLint("ValidFragment")
     public CollapsedStatusBarFragment(
             StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory,
             OngoingCallController ongoingCallController,
@@ -582,7 +580,7 @@
     }
 
     @Override
-    public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) {
+    public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator animator) {
         mSystemIconArea.setAlpha((float) animator.getAnimatedValue());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 3656ed1..22b7f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -19,8 +19,11 @@
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.LightsOutNotifController;
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarDemoMode;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
 
 import dagger.BindsInstance;
@@ -34,6 +37,9 @@
  * controllers need access to that view, so those controllers will be re-created whenever the
  * fragment is recreated.
  *
+ * Anything that depends on {@link CollapsedStatusBarFragment} or {@link PhoneStatusBarView}
+ * should be included here or in {@link StatusBarFragmentModule}.
+ *
  * Note that this is completely separate from
  * {@link com.android.systemui.statusbar.phone.dagger.StatusBarComponent}. This component gets
  * re-created on each new fragment creation, whereas
@@ -55,11 +61,13 @@
      * Initialize anything extra for the component. Must be called after the component is created.
      */
     default void init() {
-        // No one accesses this controller, so we need to make sure we reference it here so it does
-        // get initialized.
+        // No one accesses these controllers, so we need to make sure we reference them here so they
+        // do get initialized.
         getBatteryMeterViewController().init();
         getHeadsUpAppearanceController().init();
         getPhoneStatusBarViewController().init();
+        getLightsOutNotifController().init();
+        getStatusBarDemoMode().init();
     }
 
     /** */
@@ -78,4 +86,16 @@
     /** */
     @StatusBarFragmentScope
     HeadsUpAppearanceController getHeadsUpAppearanceController();
+
+    /** */
+    @StatusBarFragmentScope
+    LightsOutNotifController getLightsOutNotifController();
+
+    /** */
+    @StatusBarFragmentScope
+    StatusBarDemoMode getStatusBarDemoMode();
+
+    /** */
+    @StatusBarFragmentScope
+    PhoneStatusBarTransitions getPhoneStatusBarTransitions();
 }
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 d244558..dea1b43 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
@@ -16,13 +16,20 @@
 
 package com.android.systemui.statusbar.phone.fragment.dagger;
 
+import android.view.View;
+
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterView;
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
+
+import javax.inject.Named;
 
 import dagger.Module;
 import dagger.Provides;
@@ -30,6 +37,10 @@
 /** Dagger module for {@link StatusBarFragmentComponent}. */
 @Module
 public interface StatusBarFragmentModule {
+
+    String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
+    String OPERATOR_NAME_VIEW = "operator_name_view";
+
     /** */
     @Provides
     @RootView
@@ -49,6 +60,29 @@
     /** */
     @Provides
     @StatusBarFragmentScope
+    @Named(LIGHTS_OUT_NOTIF_VIEW)
+    static View provideLightsOutNotifView(@RootView PhoneStatusBarView view) {
+        return view.findViewById(R.id.notification_lights_out);
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
+    @Named(OPERATOR_NAME_VIEW)
+    static View provideOperatorNameView(@RootView PhoneStatusBarView view) {
+        return view.findViewById(R.id.operator_name);
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
+    static Clock provideClock(@RootView PhoneStatusBarView view) {
+        return view.findViewById(R.id.clock);
+    }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
     static PhoneStatusBarViewController providePhoneStatusBarViewController(
             PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory,
             @RootView PhoneStatusBarView phoneStatusBarView,
@@ -57,4 +91,14 @@
                 phoneStatusBarView,
                 notificationPanelViewController.getStatusBarTouchEventHandler());
     }
+
+    /** */
+    @Provides
+    @StatusBarFragmentScope
+    static PhoneStatusBarTransitions providePhoneStatusBarTransitions(
+            @RootView PhoneStatusBarView view,
+            StatusBarWindowController statusBarWindowController
+    ) {
+        return new PhoneStatusBarTransitions(view, statusBarWindowController.getBackgroundView());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index a857815..76615af0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -50,7 +50,7 @@
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.UserAvatarView;
 import com.android.systemui.util.ViewController;
 
@@ -131,7 +131,7 @@
             SysuiStatusBarStateController statusBarStateController,
             DozeParameters dozeParameters,
             Provider<UserDetailView.Adapter> userDetailViewAdapterProvider,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
+            ScreenOffAnimationController screenOffAnimationController,
             FeatureFlags featureFlags,
             UserSwitchDialogController userSwitchDialogController) {
         super(view);
@@ -146,7 +146,7 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, communalStateController,
                 keyguardStateController, dozeParameters,
-                unlockedScreenOffAnimationController,  /* animateYPos= */ false,
+                screenOffAnimationController,  /* animateYPos= */ false,
                 /* visibleOnCommunal= */ false);
         mUserDetailAdapter = new KeyguardUserDetailAdapter(context, userDetailViewAdapterProvider);
         mFeatureFlags = featureFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index c4b5961..ffa7963 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -52,7 +52,7 @@
 import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.util.ViewController;
 
 import java.util.ArrayList;
@@ -163,7 +163,7 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DozeParameters dozeParameters,
-            UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
+            ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardUserSwitcherView);
         if (DEBUG) Log.d(TAG, "New KeyguardUserSwitcherController");
         mContext = context;
@@ -176,7 +176,7 @@
                 mUserSwitcherController, this);
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, communalStateController,
                 keyguardStateController, dozeParameters,
-                unlockedScreenOffAnimationController, /* animateYPos= */ false,
+                screenOffAnimationController, /* animateYPos= */ false,
                 /* visibleOnCommunal= */ false);
         mBackground = new KeyguardUserSwitcherScrim(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
deleted file mode 100644
index ac8b47d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.java
+++ /dev/null
@@ -1,86 +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.systemui.statusbar.policy;
-
-import android.app.StatusBarManager;
-import android.content.Context;
-import android.content.res.Configuration;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.phone.StatusBar;
-
-import javax.inject.Inject;
-
-/**
- * Let {@link RemoteInputView} to control the visibility of QuickSetting.
- */
-@SysUISingleton
-public class RemoteInputQuickSettingsDisabler
-        implements ConfigurationController.ConfigurationListener {
-
-    private Context mContext;
-    @VisibleForTesting boolean mRemoteInputActive;
-    @VisibleForTesting boolean misLandscape;
-    private int mLastOrientation;
-    private final CommandQueue mCommandQueue;
-
-    @Inject
-    public RemoteInputQuickSettingsDisabler(Context context,
-            ConfigurationController configController, CommandQueue commandQueue) {
-        mContext = context;
-        mCommandQueue = commandQueue;
-        mLastOrientation = mContext.getResources().getConfiguration().orientation;
-        configController.addCallback(this);
-    }
-
-    public int adjustDisableFlags(int state) {
-        if (mRemoteInputActive && misLandscape) {
-            state |= StatusBarManager.DISABLE2_QUICK_SETTINGS;
-        }
-
-        return state;
-    }
-
-    public void setRemoteInputActive(boolean active){
-        if(mRemoteInputActive != active){
-            mRemoteInputActive = active;
-            recomputeDisableFlags();
-        }
-    }
-
-    @Override
-    public void onConfigChanged(Configuration newConfig) {
-        if (newConfig.orientation != mLastOrientation) {
-            misLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
-            mLastOrientation = newConfig.orientation;
-            recomputeDisableFlags();
-        }
-    }
-
-    /**
-     * Reapplies the disable flags. Then the method adjustDisableFlags in this class will be invoked
-     * in {@link QSFragment#disable(int, int, boolean)} and
-     * {@link StatusBar#disable(int, int, boolean)}
-     * to modify the disable flags according to the status of mRemoteInputActive and misLandscape.
-     */
-    private void recomputeDisableFlags() {
-        mCommandQueue.recomputeDisableFlags(mContext.getDisplayId(), true);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
new file mode 100644
index 0000000..31ef2f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.systemui.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/**
+ * Controls whether the disable flag [StatusBarManager.DISABLE2_QUICK_SETTINGS] should be set.
+ * This would happen when a [RemoteInputView] is active, the device is in landscape and not using
+ * split shade.
+ */
+@SysUISingleton
+class RemoteInputQuickSettingsDisabler @Inject constructor(
+    private val context: Context,
+    private val commandQueue: CommandQueue,
+    configController: ConfigurationController
+) : ConfigurationController.ConfigurationListener {
+
+    private var remoteInputActive = false
+    private var isLandscape: Boolean
+    private var shouldUseSplitNotificationShade: Boolean
+
+    init {
+        isLandscape =
+            context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+        shouldUseSplitNotificationShade = Utils.shouldUseSplitNotificationShade(context.resources)
+        configController.addCallback(this)
+    }
+
+    fun adjustDisableFlags(state: Int): Int {
+        var mutableState = state
+        if (remoteInputActive &&
+            isLandscape &&
+            !shouldUseSplitNotificationShade
+        ) {
+            mutableState = state or StatusBarManager.DISABLE2_QUICK_SETTINGS
+        }
+        return mutableState
+    }
+
+    fun setRemoteInputActive(active: Boolean) {
+        if (remoteInputActive != active) {
+            remoteInputActive = active
+            recomputeDisableFlags()
+        }
+    }
+
+    override fun onConfigChanged(newConfig: Configuration) {
+        var needToRecompute = false
+
+        val newIsLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+        if (newIsLandscape != isLandscape) {
+            isLandscape = newIsLandscape
+            needToRecompute = true
+        }
+
+        val newSplitShadeFlag = Utils.shouldUseSplitNotificationShade(context.resources)
+        if (newSplitShadeFlag != shouldUseSplitNotificationShade) {
+            shouldUseSplitNotificationShade = newSplitShadeFlag
+            needToRecompute = true
+        }
+        if (needToRecompute) {
+            recomputeDisableFlags()
+        }
+    }
+
+    /**
+     * Called in order to trigger a refresh of the disable flags after a relevant configuration
+     * change or when a [RemoteInputView] has changed its active state. The method
+     * [adjustDisableFlags] will be invoked to modify the disable flags according to
+     * [remoteInputActive], [isLandscape] and [shouldUseSplitNotificationShade].
+     */
+    private fun recomputeDisableFlags() {
+        commandQueue.recomputeDisableFlags(context.displayId, true)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 17dd26a..79ee746 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -836,6 +836,11 @@
         mRootView = notificationShadeWindowView;
     }
 
+    @VisibleForTesting
+    public KeyguardStateController getKeyguardStateController() {
+        return mKeyguardStateController;
+    }
+
     public static abstract class BaseUserAdapter extends BaseAdapter {
 
         final UserSwitcherController mController;
@@ -843,7 +848,7 @@
 
         protected BaseUserAdapter(UserSwitcherController controller) {
             mController = controller;
-            mKeyguardStateController = controller.mKeyguardStateController;
+            mKeyguardStateController = controller.getKeyguardStateController();
             controller.addAdapter(new WeakReference<>(this));
         }
 
@@ -1161,7 +1166,7 @@
                     ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
                     : R.string.guest_exit_guest_dialog_title);
             setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
-            setButton(DialogInterface.BUTTON_NEGATIVE,
+            setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(mGuestUserAutoCreated
@@ -1180,7 +1185,7 @@
             if (mFalsingManager.isFalseTap(penalty)) {
                 return;
             }
-            if (which == BUTTON_NEGATIVE) {
+            if (which == BUTTON_NEUTRAL) {
                 cancel();
             } else {
                 mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
@@ -1198,7 +1203,7 @@
             super(context);
             setTitle(R.string.user_add_user_title);
             setMessage(context.getString(R.string.user_add_user_message_short));
-            setButton(DialogInterface.BUTTON_NEGATIVE,
+            setButton(DialogInterface.BUTTON_NEUTRAL,
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(android.R.string.ok), this);
@@ -1212,7 +1217,7 @@
             if (mFalsingManager.isFalseTap(penalty)) {
                 return;
             }
-            if (which == BUTTON_NEGATIVE) {
+            if (which == BUTTON_NEUTRAL) {
                 cancel();
             } else {
                 mDialogLaunchAnimator.dismissStack(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 8ea7d62..fb6861d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.tv;
 
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -38,6 +39,10 @@
 @SysUISingleton
 public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks {
 
+    private static final String ACTION_SHOW_PIP_MENU =
+            "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+    private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
     private final CommandQueue mCommandQueue;
     private final Lazy<AssistManager> mAssistManagerLazy;
 
@@ -65,4 +70,9 @@
     public void startAssist(Bundle args) {
         mAssistManagerLazy.get().startAssist(args);
     }
+
+    @Override
+    public void showPictureInPictureMenu() {
+        mContext.sendBroadcast(new Intent(ACTION_SHOW_PIP_MENU), SYSTEMUI_PERMISSION);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 700b58a..bd84520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -64,7 +64,6 @@
     private final WindowManager mWindowManager;
     private final IWindowManager mIWindowManager;
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
-    private final Resources mResources;
     private int mBarHeight = -1;
     private final State mCurrentState = new State();
 
@@ -91,7 +90,6 @@
         mLaunchAnimationContainer = mStatusBarWindowView.findViewById(
                 R.id.status_bar_launch_animation_container);
         mLpChanged = new WindowManager.LayoutParams();
-        mResources = resources;
 
         if (mBarHeight < 0) {
             mBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 39544fb..c86d77b 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -48,6 +48,8 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.TypedValue;
 
 import androidx.annotation.NonNull;
@@ -105,14 +107,15 @@
     private final UserManager mUserManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private final Executor mBgExecutor;
-    private SecureSettings mSecureSettings;
+    private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final Handler mBgHandler;
     private final boolean mIsMonetEnabled;
-    private UserTracker mUserTracker;
-    private DeviceProvisionedController mDeviceProvisionedController;
-    private WallpaperColors mCurrentColors;
-    private WallpaperManager mWallpaperManager;
+    private final UserTracker mUserTracker;
+    private final DeviceProvisionedController mDeviceProvisionedController;
+    // Current wallpaper colors associated to a user.
+    private final SparseArray<WallpaperColors> mCurrentColors = new SparseArray<>();
+    private final WallpaperManager mWallpaperManager;
     private ColorScheme mColorScheme;
     // If fabricated overlays were already created for the current theme.
     private boolean mNeedsOverlayCreation;
@@ -126,11 +129,11 @@
     private FabricatedOverlay mNeutralOverlay;
     // If wallpaper color event will be accepted and change the UI colors.
     private boolean mAcceptColorEvents = true;
-    // If non-null, colors that were sent to the framework, and processing was deferred until
-    // the next time the screen is off.
-    private WallpaperColors mDeferredWallpaperColors;
-    private int mDeferredWallpaperColorsFlags;
-    private WakefulnessLifecycle mWakefulnessLifecycle;
+    // If non-null (per user), colors that were sent to the framework, and processing was deferred
+    // until the next time the screen is off.
+    private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>();
+    private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray();
+    private final WakefulnessLifecycle mWakefulnessLifecycle;
 
     // Defers changing themes until Setup Wizard is done.
     private boolean mDeferredThemeEvaluation;
@@ -153,27 +156,53 @@
                 }
             };
 
-    private final OnColorsChangedListener mOnColorsChangedListener = (wallpaperColors, which) -> {
-        if (!mAcceptColorEvents && mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
-            mDeferredWallpaperColors = wallpaperColors;
-            mDeferredWallpaperColorsFlags = which;
-            Log.i(TAG, "colors received; processing deferred until screen off: " + wallpaperColors);
-            return;
+    private final OnColorsChangedListener mOnColorsChangedListener = new OnColorsChangedListener() {
+        @Override
+        public void onColorsChanged(WallpaperColors wallpaperColors, int which) {
+            throw new IllegalStateException("This should never be invoked, all messages should "
+                    + "arrive on the overload that has a user id");
         }
 
-        if (wallpaperColors != null) {
-            mAcceptColorEvents = false;
-            // Any cache of colors deferred for process is now stale.
-            mDeferredWallpaperColors = null;
-            mDeferredWallpaperColorsFlags = 0;
-        }
+        @Override
+        public void onColorsChanged(WallpaperColors wallpaperColors, int which, int userId) {
+            boolean currentUser = userId == mUserTracker.getUserId();
+            if (currentUser && !mAcceptColorEvents
+                    && mWakefulnessLifecycle.getWakefulness() != WAKEFULNESS_ASLEEP) {
+                mDeferredWallpaperColors.put(userId, wallpaperColors);
+                mDeferredWallpaperColorsFlags.put(userId, which);
+                Log.i(TAG, "colors received; processing deferred until screen off: "
+                        + wallpaperColors + " user: " + userId);
+                return;
+            }
 
-        handleWallpaperColors(wallpaperColors, which);
+            if (currentUser && wallpaperColors != null) {
+                mAcceptColorEvents = false;
+                // Any cache of colors deferred for process is now stale.
+                mDeferredWallpaperColors.put(userId, null);
+                mDeferredWallpaperColorsFlags.put(userId, 0);
+            }
+
+            handleWallpaperColors(wallpaperColors, which, userId);
+        }
     };
 
-    private int getLatestWallpaperType() {
-        return mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)
-                > mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM)
+    private final UserTracker.Callback mUserTrackerCallback = new UserTracker.Callback() {
+        @Override
+        public void onUserChanged(int newUser, @NonNull Context userContext) {
+            boolean isManagedProfile = mUserManager.isManagedProfile(newUser);
+            if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) {
+                Log.i(TAG, "User setup not finished when new user event was received. "
+                        + "Deferring... Managed profile? " + isManagedProfile);
+                return;
+            }
+            if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
+            reevaluateSystemTheme(true /* forceReload */);
+        }
+    };
+
+    private int getLatestWallpaperType(int userId) {
+        return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+                > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
                 ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
     }
 
@@ -205,14 +234,21 @@
         return false;
     }
 
-    private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags) {
-        final boolean hadWallpaperColors = mCurrentColors != null;
-        int latestWallpaperType = getLatestWallpaperType();
+    private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
+        final int currentUser = mUserTracker.getUserId();
+        final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
+        int latestWallpaperType = getLatestWallpaperType(userId);
         if ((flags & latestWallpaperType) != 0) {
-            mCurrentColors = wallpaperColors;
+            mCurrentColors.put(userId, wallpaperColors);
             if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
         }
 
+        if (userId != currentUser) {
+            Log.d(TAG, "Colors " + wallpaperColors + " for user " + userId + ". "
+                    + "Not for current user: " + currentUser);
+            return;
+        }
+
         if (mDeviceProvisionedController != null
                 && !mDeviceProvisionedController.isCurrentUserSetup()) {
             if (hadWallpaperColors) {
@@ -227,13 +263,12 @@
             } else {
                 if (DEBUG) {
                     Log.i(TAG, "During user setup, but allowing first color event: had? "
-                            + hadWallpaperColors + " has? " + (mCurrentColors != null));
+                            + hadWallpaperColors + " has? " + (mCurrentColors.get(userId) != null));
                 }
             }
         }
         // Check if we need to reset to default colors (if a color override was set that is sourced
         // from the wallpaper)
-        int currentUser = mUserTracker.getUserId();
         String overlayPackageJson = mSecureSettings.getStringForUser(
                 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
                 currentUser);
@@ -279,10 +314,9 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             boolean newWorkProfile = Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction());
-            boolean userStarted = Intent.ACTION_USER_SWITCHED.equals(intent.getAction());
             boolean isManagedProfile = mUserManager.isManagedProfile(
                     intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-            if (userStarted || newWorkProfile) {
+            if (newWorkProfile) {
                 if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) {
                     Log.i(TAG, "User setup not finished when " + intent.getAction()
                             + " was received. Deferring... Managed profile? " + isManagedProfile);
@@ -331,7 +365,6 @@
     public void start() {
         if (DEBUG) Log.d(TAG, "Start");
         final IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
         filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
@@ -366,6 +399,8 @@
             return;
         }
 
+        mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
 
         // All wallpaper color and keyguard logic only applies when Monet is enabled.
@@ -376,10 +411,10 @@
         // Upon boot, make sure we have the most up to date colors
         Runnable updateColors = () -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
-                    getLatestWallpaperType());
+                    getLatestWallpaperType(mUserTracker.getUserId()));
             Runnable applyColors = () -> {
                 if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
-                mCurrentColors = systemColor;
+                mCurrentColors.put(mUserTracker.getUserId(), systemColor);
                 reevaluateSystemTheme(false /* forceReload */);
             };
             if (mDeviceProvisionedController.isCurrentUserSetup()) {
@@ -401,21 +436,22 @@
         mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
             @Override
             public void onFinishedGoingToSleep() {
-                if (mDeferredWallpaperColors != null) {
-                    WallpaperColors colors = mDeferredWallpaperColors;
-                    int flags = mDeferredWallpaperColorsFlags;
+                final int userId = mUserTracker.getUserId();
+                final WallpaperColors colors = mDeferredWallpaperColors.get(userId);
+                if (colors != null) {
+                    int flags = mDeferredWallpaperColorsFlags.get(userId);
 
-                    mDeferredWallpaperColors = null;
-                    mDeferredWallpaperColorsFlags = 0;
+                    mDeferredWallpaperColors.put(userId, null);
+                    mDeferredWallpaperColorsFlags.put(userId, 0);
 
-                    handleWallpaperColors(colors, flags);
+                    handleWallpaperColors(colors, flags, userId);
                 }
             }
         });
     }
 
     private void reevaluateSystemTheme(boolean forceReload) {
-        final WallpaperColors currentColors = mCurrentColors;
+        final WallpaperColors currentColors = mCurrentColors.get(mUserTracker.getUserId());
         final int mainColor;
         final int accentCandidate;
         if (currentColors == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 5b66216..3231aec 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -54,6 +54,7 @@
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setTheme(R.style.Theme_AppCompat_DayNight);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
         requestWindowFeature(Window.FEATURE_NO_TITLE);
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
new file mode 100644
index 0000000..fb9df01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.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.unfold
+
+import android.os.PowerManager
+import android.provider.Settings
+import com.android.systemui.keyguard.KeyguardViewMediator
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.statusbar.phone.ScreenOffAnimation
+import com.android.systemui.statusbar.phone.StatusBar
+import com.android.systemui.statusbar.policy.CallbackController
+import com.android.systemui.unfold.FoldAodAnimationController.FoldAodAnimationStatus
+import com.android.systemui.util.settings.GlobalSettings
+import dagger.Lazy
+import javax.inject.Inject
+
+/**
+ * Controls folding to AOD animation: when AOD is enabled and foldable device is folded
+ * we play a special AOD animation on the outer screen
+ */
+@SysUIUnfoldScope
+class FoldAodAnimationController @Inject constructor(
+    private val screenLifecycle: ScreenLifecycle,
+    private val keyguardViewMediatorLazy: Lazy<KeyguardViewMediator>,
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+    private val globalSettings: GlobalSettings
+) : ScreenLifecycle.Observer,
+    CallbackController<FoldAodAnimationStatus>,
+    ScreenOffAnimation,
+    WakefulnessLifecycle.Observer {
+
+    private var alwaysOnEnabled: Boolean = false
+    private var isScrimOpaque: Boolean = false
+    private lateinit var statusBar: StatusBar
+    private var pendingScrimReadyCallback: Runnable? = null
+
+    private var shouldPlayAnimation = false
+    private val statusListeners = arrayListOf<FoldAodAnimationStatus>()
+
+    private var isAnimationPlaying = false
+
+    override fun initialize(statusBar: StatusBar, lightRevealScrim: LightRevealScrim) {
+        this.statusBar = statusBar
+
+        screenLifecycle.addObserver(this)
+        wakefulnessLifecycle.addObserver(this)
+    }
+
+    /**
+     * Returns true if we should run fold to AOD animation
+     */
+    override fun shouldPlayAnimation(): Boolean =
+        shouldPlayAnimation
+
+    override fun startAnimation(): Boolean =
+        if (alwaysOnEnabled &&
+            wakefulnessLifecycle.lastSleepReason == PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD &&
+            globalSettings.getString(Settings.Global.ANIMATOR_DURATION_SCALE) != "0"
+        ) {
+            shouldPlayAnimation = true
+
+            isAnimationPlaying = true
+            statusBar.notificationPanelViewController.prepareFoldToAodAnimation()
+
+            statusListeners.forEach(FoldAodAnimationStatus::onFoldToAodAnimationChanged)
+
+            true
+        } else {
+            shouldPlayAnimation = false
+            false
+        }
+
+    override fun onStartedWakingUp() {
+        shouldPlayAnimation = false
+        isAnimationPlaying = false
+    }
+
+    /**
+     * Called when screen starts turning on, the contents of the screen might not be visible yet.
+     * This method reports back that the animation is ready in [onReady] callback.
+     *
+     * @param onReady callback when the animation is ready
+     * @see [com.android.systemui.keyguard.KeyguardViewMediator]
+     */
+    fun onScreenTurningOn(onReady: Runnable) {
+        if (shouldPlayAnimation) {
+            if (isScrimOpaque) {
+                onReady.run()
+            } else {
+                pendingScrimReadyCallback = onReady
+            }
+        } else {
+            // No animation, call ready callback immediately
+            onReady.run()
+        }
+    }
+
+    /**
+     * Called when keyguard scrim opaque changed
+     */
+    override fun onScrimOpaqueChanged(isOpaque: Boolean) {
+        isScrimOpaque = isOpaque
+
+        if (isOpaque) {
+            pendingScrimReadyCallback?.run()
+            pendingScrimReadyCallback = null
+        }
+    }
+
+    override fun onScreenTurnedOn() {
+        if (shouldPlayAnimation) {
+            statusBar.notificationPanelViewController.startFoldToAodAnimation {
+                // End action
+                isAnimationPlaying = false
+                keyguardViewMediatorLazy.get().maybeHandlePendingLock()
+            }
+            shouldPlayAnimation = false
+        }
+    }
+
+    override fun isAnimationPlaying(): Boolean =
+        isAnimationPlaying
+
+    override fun isKeyguardHideDelayed(): Boolean =
+        isAnimationPlaying()
+
+    override fun shouldShowAodIconsWhenShade(): Boolean =
+        shouldPlayAnimation()
+
+    override fun shouldAnimateAodIcons(): Boolean =
+        !shouldPlayAnimation()
+
+    override fun shouldAnimateDozingChange(): Boolean =
+        !shouldPlayAnimation()
+
+    override fun shouldAnimateClockChange(): Boolean =
+        !isAnimationPlaying()
+
+    /**
+     * Called when AOD status is changed
+     */
+    override fun onAlwaysOnChanged(alwaysOn: Boolean) {
+        alwaysOnEnabled = alwaysOn
+    }
+
+    override fun addCallback(listener: FoldAodAnimationStatus) {
+        statusListeners += listener
+    }
+
+    override fun removeCallback(listener: FoldAodAnimationStatus) {
+        statusListeners.remove(listener)
+    }
+
+    interface FoldAodAnimationStatus {
+        fun onFoldToAodAnimationChanged()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index b53ab21..ccde316 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -78,6 +78,8 @@
 
     fun getStatusBarMoveFromCenterAnimationController(): StatusBarMoveFromCenterAnimationController
 
+    fun getFoldAodAnimationController(): FoldAodAnimationController
+
     fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
 
     fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
new file mode 100644
index 0000000..7f63d6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.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.systemui.unfold
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.keyguard.ScreenLifecycle
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Logs performance metrics regarding time to turn the inner screen on.
+ *
+ * This class assumes that [onFoldEvent] is always called before [onScreenTurnedOn].
+ * This should be used from only one process.
+ * For now, the focus is on the time the inner display is visible, but in the future, it is easily
+ * possible to monitor the time to go from the inner screen to the outer.
+ */
+@SysUISingleton
+class UnfoldLatencyTracker @Inject constructor(
+    private val latencyTracker: LatencyTracker,
+    private val deviceStateManager: DeviceStateManager,
+    @UiBackground private val uiBgExecutor: Executor,
+    private val context: Context,
+    private val screenLifecycle: ScreenLifecycle
+) : ScreenLifecycle.Observer {
+
+    private var folded: Boolean? = null
+    private val foldStateListener = FoldStateListener(context)
+    private val isFoldable: Boolean
+        get() = context.resources.getIntArray(
+            com.android.internal.R.array.config_foldedDeviceStates).isNotEmpty()
+
+    /** Registers for relevant events only if the device is foldable. */
+    fun init() {
+        if (!isFoldable) {
+            return
+        }
+        deviceStateManager.registerCallback(uiBgExecutor, foldStateListener)
+        screenLifecycle.addObserver(this)
+    }
+
+    /**
+     * To be called when the screen becomes visible.
+     *
+     * This is safe to call also when unsure whether the device is not a foldable, as it emits the
+     * end action event only if we previously received a fold state.
+     */
+    override fun onScreenTurnedOn() {
+        if (folded == false) {
+            latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+        }
+    }
+
+    private fun onFoldEvent(folded: Boolean) {
+        if (this.folded != folded) {
+            this.folded = folded
+            if (!folded) { // unfolding started
+                latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+            }
+        }
+    }
+
+    private inner class FoldStateListener(context: Context) :
+        DeviceStateManager.FoldStateListener(context, { onFoldEvent(it) })
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 51de1321..e6fc49f 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.unfold
 
+import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.PixelFormat
 import android.hardware.devicestate.DeviceStateManager
@@ -111,7 +112,7 @@
         Trace.beginSection("UnfoldLightRevealOverlayAnimation#onScreenTurningOn")
         try {
             // Add the view only if we are unfolding and this is the first screen on
-            if (!isFolded && !isUnfoldHandled) {
+            if (!isFolded && !isUnfoldHandled && ValueAnimator.areAnimatorsEnabled()) {
                 addView(onOverlayReady)
                 isUnfoldHandled = true
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
index 0f4193e9..4f20067 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -39,9 +39,17 @@
     fun remove(element: E): Boolean = listeners.remove(element)
 
     /**
+     * Determine if the listener set is empty
+     */
+    fun isEmpty(): Boolean = listeners.isEmpty()
+
+    /**
      * Returns an iterator over the listeners currently in the set.  Note that to ensure
      * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes
      * made to the set after the iterator is constructed.
      */
     override fun iterator(): Iterator<E> = listeners.iterator()
 }
+
+/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */
+fun <T> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java
rename to packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
index 734ab63..0bbf56c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.communal.conditions;
+package com.android.systemui.util.condition;
 
 import android.util.Log;
 
@@ -27,9 +27,10 @@
 import java.util.Iterator;
 
 /**
- * Base class for a condition that needs to be fulfilled in order for Communal Mode to display.
+ * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
+ * its callbacks.
  */
-public abstract class CommunalCondition implements CallbackController<CommunalCondition.Callback> {
+public abstract class Condition implements CallbackController<Condition.Callback> {
     private final String mTag = getClass().getSimpleName();
 
     private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
@@ -125,6 +126,6 @@
          * @param condition The condition in question.
          * @param isConditionMet True if the condition has been fulfilled. False otherwise.
          */
-        void onConditionChanged(CommunalCondition condition, boolean isConditionMet);
+        void onConditionChanged(Condition condition, boolean isConditionMet);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
rename to packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4f772da..8b6e982 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -14,43 +14,36 @@
  * limitations under the License.
  */
 
-package com.android.systemui.communal.conditions;
-
-import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS;
+package com.android.systemui.util.condition;
 
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.policy.CallbackController;
 
 import org.jetbrains.annotations.NotNull;
 
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Set;
 
 import javax.inject.Inject;
-import javax.inject.Named;
 
 /**
- * {@link CommunalConditionsMonitor} takes in a set of conditions, monitors whether all of them have
+ * {@link Monitor} takes in a set of conditions, monitors whether all of them have
  * been fulfilled, and informs any registered listeners.
  */
-@SysUISingleton
-public class CommunalConditionsMonitor implements
-        CallbackController<CommunalConditionsMonitor.Callback> {
+public class Monitor implements CallbackController<Monitor.Callback> {
     private final String mTag = getClass().getSimpleName();
 
-    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
 
     // Set of all conditions that need to be monitored.
-    private final Set<CommunalCondition> mConditions;
+    private final Set<Condition> mConditions;
 
     // Map of values of each condition.
-    private final HashMap<CommunalCondition, Boolean> mConditionsMap = new HashMap<>();
+    private final HashMap<Condition, Boolean> mConditionsMap = new HashMap<>();
 
     // Whether all conditions have been met.
     private boolean mAllConditionsMet = false;
@@ -59,7 +52,7 @@
     private boolean mHaveConditionsStarted = false;
 
     // Callback for when each condition has been updated.
-    private final CommunalCondition.Callback mConditionCallback = (condition, isConditionMet) -> {
+    private final Condition.Callback mConditionCallback = (condition, isConditionMet) -> {
         mConditionsMap.put(condition, isConditionMet);
 
         final boolean newAllConditionsMet = !mConditionsMap.containsValue(false);
@@ -72,9 +65,9 @@
         mAllConditionsMet = newAllConditionsMet;
 
         // Updates all callbacks.
-        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        final Iterator<Callback> iterator = mCallbacks.iterator();
         while (iterator.hasNext()) {
-            final Callback callback = iterator.next().get();
+            final Callback callback = iterator.next();
             if (callback == null) {
                 iterator.remove();
             } else {
@@ -84,18 +77,31 @@
     };
 
     @Inject
-    public CommunalConditionsMonitor(
-            @Named(COMMUNAL_CONDITIONS) Set<CommunalCondition> communalConditions) {
-        mConditions = communalConditions;
+    public Monitor(Set<Condition> conditions, Set<Callback> callbacks) {
+        mConditions = conditions;
+
+        // If there is no condition, give green pass.
+        if (mConditions.isEmpty()) {
+            mAllConditionsMet = true;
+            return;
+        }
 
         // Initializes the conditions map and registers a callback for each condition.
         mConditions.forEach((condition -> mConditionsMap.put(condition, false)));
+
+        if (callbacks == null) {
+            return;
+        }
+
+        for (Callback callback : callbacks) {
+            addCallback(callback);
+        }
     }
 
     @Override
     public void addCallback(@NotNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "adding callback");
-        mCallbacks.add(new WeakReference<>(callback));
+        mCallbacks.add(callback);
 
         // Updates the callback immediately.
         callback.onConditionsChanged(mAllConditionsMet);
@@ -110,9 +116,9 @@
     @Override
     public void removeCallback(@NotNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "removing callback");
-        final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
+        final Iterator<Callback> iterator = mCallbacks.iterator();
         while (iterator.hasNext()) {
-            final Callback cb = iterator.next().get();
+            final Callback cb = iterator.next();
             if (cb == null || cb == callback) {
                 iterator.remove();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
new file mode 100644
index 0000000..fc67973
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
@@ -0,0 +1,46 @@
+/*
+ * 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.condition.dagger;
+
+import com.android.systemui.util.condition.Condition;
+import com.android.systemui.util.condition.Monitor;
+
+import java.util.Set;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Component for {@link Monitor}.
+ */
+@Subcomponent
+public interface MonitorComponent {
+    /**
+     * Factory for {@link MonitorComponent}.
+     */
+    @Subcomponent.Factory
+    interface Factory {
+        MonitorComponent create(@BindsInstance Set<Condition> conditions,
+                @BindsInstance Set<Monitor.Callback> callbacks);
+    }
+
+    /**
+     * Provides {@link Monitor}.
+     * @return
+     */
+    Monitor getMonitor();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index 981bf01..7892d6e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -18,6 +18,7 @@
 
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.RingerModeTrackerImpl;
+import com.android.systemui.util.condition.dagger.MonitorComponent;
 import com.android.systemui.util.wrapper.UtilWrapperModule;
 
 import dagger.Binds;
@@ -26,6 +27,9 @@
 /** Dagger Module for code in the util package. */
 @Module(includes = {
                 UtilWrapperModule.class
+        },
+        subcomponents = {
+                MonitorComponent.class,
         })
 public interface UtilModule {
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index bad0c37..c57dbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.util.service;
 
-import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
-import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -26,6 +24,8 @@
 import android.os.IBinder;
 import android.util.Log;
 
+import com.android.systemui.dagger.qualifiers.Main;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -35,6 +35,8 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import javax.inject.Inject;
+
 /**
  * {@link ObservableServiceConnection} is a concrete implementation of {@link ServiceConnection}
  * that enables monitoring the status of a binder connection. It also aides in automatically
@@ -119,17 +121,17 @@
      * Default constructor for {@link ObservableServiceConnection}.
      * @param context The context from which the service will be bound with.
      * @param serviceIntent The intent to  bind service with.
-     * @param flags The flags to use during the binding
      * @param executor The executor for connection callbacks to be delivered on
      * @param transformer A {@link ServiceTransformer} for transforming the resulting service
      *                    into a desired type.
      */
+    @Inject
     public ObservableServiceConnection(Context context, Intent serviceIntent,
-            @Context.BindServiceFlags int flags, @NonNull @CallbackExecutor Executor executor,
+            @Main Executor executor,
             ServiceTransformer<T> transformer) {
         mContext = context;
         mServiceIntent = serviceIntent;
-        mFlags = flags;
+        mFlags = Context.BIND_AUTO_CREATE;
         mExecutor = executor;
         mTransformer = transformer;
         mCallbacks = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 6dd6d6d..4600bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -56,6 +56,7 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.tracing.nano.SystemUiTraceProto;
 import com.android.wm.shell.ShellCommandHandler;
+import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.draganddrop.DragAndDrop;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -66,7 +67,6 @@
 import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.protolog.ShellProtoLogImpl;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
 import java.io.FileDescriptor;
@@ -114,7 +114,7 @@
     private final Optional<OneHanded> mOneHandedOptional;
     private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
     private final Optional<ShellCommandHandler> mShellCommandHandler;
-    private final Optional<SizeCompatUI> mSizeCompatUIOptional;
+    private final Optional<CompatUI> mCompatUIOptional;
     private final Optional<DragAndDrop> mDragAndDropOptional;
 
     private final CommandQueue mCommandQueue;
@@ -132,7 +132,7 @@
     private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
     private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
     private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
-    private KeyguardUpdateMonitorCallback mSizeCompatUIKeyguardCallback;
+    private KeyguardUpdateMonitorCallback mCompatUIKeyguardCallback;
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
 
     @Inject
@@ -143,7 +143,7 @@
             Optional<OneHanded> oneHandedOptional,
             Optional<HideDisplayCutout> hideDisplayCutoutOptional,
             Optional<ShellCommandHandler> shellCommandHandler,
-            Optional<SizeCompatUI> sizeCompatUIOptional,
+            Optional<CompatUI> sizeCompatUIOptional,
             Optional<DragAndDrop> dragAndDropOptional,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
@@ -169,7 +169,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mProtoTracer = protoTracer;
         mShellCommandHandler = shellCommandHandler;
-        mSizeCompatUIOptional = sizeCompatUIOptional;
+        mCompatUIOptional = sizeCompatUIOptional;
         mDragAndDropOptional = dragAndDropOptional;
         mSysUiMainExecutor = sysUiMainExecutor;
     }
@@ -185,7 +185,7 @@
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
         mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
-        mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi);
+        mCompatUIOptional.ifPresent(this::initCompatUi);
         mDragAndDropOptional.ifPresent(this::initDragAndDrop);
     }
 
@@ -391,14 +391,14 @@
     }
 
     @VisibleForTesting
-    void initSizeCompatUi(SizeCompatUI sizeCompatUI) {
-        mSizeCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() {
+    void initCompatUi(CompatUI sizeCompatUI) {
+        mCompatUIKeyguardCallback = new KeyguardUpdateMonitorCallback() {
             @Override
             public void onKeyguardOccludedChanged(boolean occluded) {
                 sizeCompatUI.onKeyguardOccludedChanged(occluded);
             }
         };
-        mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback);
+        mKeyguardUpdateMonitor.registerCallback(mCompatUIKeyguardCallback);
     }
 
     void initDragAndDrop(DragAndDrop dragAndDrop) {
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index fc353a1..5ffd300 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -18,12 +18,17 @@
         <option name="test-file-name" value="SystemUITests.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
+
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="framework-base-presubmit" />
     <option name="test-tag" value="SystemUITests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.systemui.tests" />
         <option name="runner" value="android.testing.TestableInstrumentation" />
+        <option name="test-filter-dir" value="/data/data/com.android.systemui.tests" />
         <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index e967033..74e0f40 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -264,7 +264,7 @@
         reset(mView);
         observer.onChange(true);
         mExecutor.runAllReady();
-        verify(mView).switchToClock(KeyguardClockSwitch.SMALL);
+        verify(mView).switchToClock(KeyguardClockSwitch.SMALL, /* animate */ true);
     }
 
     private void verifyAttachment(VerificationMode times) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index e4336fe..8717a0e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -253,8 +253,8 @@
     }
 
     @Test
-    public void switchingToBigClock_makesSmallClockDisappear() {
-        mKeyguardClockSwitch.switchToClock(LARGE);
+    public void switchingToBigClockWithAnimation_makesSmallClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true);
 
         mKeyguardClockSwitch.mClockInAnim.end();
         mKeyguardClockSwitch.mClockOutAnim.end();
@@ -265,8 +265,17 @@
     }
 
     @Test
-    public void switchingToSmallClock_makesBigClockDisappear() {
-        mKeyguardClockSwitch.switchToClock(SMALL);
+    public void switchingToBigClockNoAnimation_makesSmallClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ false);
+
+        assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+    }
+
+    @Test
+    public void switchingToSmallClockWithAnimation_makesBigClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(SMALL, /* animate */ true);
 
         mKeyguardClockSwitch.mClockInAnim.end();
         mKeyguardClockSwitch.mClockOutAnim.end();
@@ -279,8 +288,19 @@
     }
 
     @Test
+    public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
+        mKeyguardClockSwitch.switchToClock(SMALL, false);
+
+        assertThat(mClockFrame.getAlpha()).isEqualTo(1);
+        assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+        // only big clock is removed at switch
+        assertThat(mLargeClockFrame.getParent()).isNull();
+        assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
+    }
+
+    @Test
     public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
-        assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isTrue();
-        assertThat(mKeyguardClockSwitch.switchToClock(LARGE)).isFalse();
+        assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
+        assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 030464a..c873804 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -48,8 +48,10 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.plugins.FalsingManager;
 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.settings.GlobalSettings;
 
 import org.junit.Before;
@@ -110,7 +112,11 @@
     @Mock
     private FalsingCollector mFalsingCollector;
     @Mock
+    private FalsingManager mFalsingManager;
+    @Mock
     private GlobalSettings mGlobalSettings;
+    @Mock
+    private UserSwitcherController mUserSwitcherController;
     private Configuration mConfiguration;
 
     private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -144,8 +150,8 @@
                 mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
                 mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
                 mKeyguardStateController, mKeyguardSecurityViewFlipperController,
-                mConfigurationController, mFalsingCollector, mGlobalSettings)
-                .create(mSecurityCallback);
+                mConfigurationController, mFalsingCollector, mFalsingManager,
+                mUserSwitcherController, mGlobalSettings).create(mSecurityCallback);
     }
 
     @Test
@@ -182,13 +188,15 @@
     public void onResourcesUpdate_callsThroughOnRotationChange() {
         // Rotation is the same, shouldn't cause an update
         mKeyguardSecurityContainerController.updateResources();
-        verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings);
+        verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
 
         // Update rotation. Should trigger update
         mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
 
         mKeyguardSecurityContainerController.updateResources();
-        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings);
+        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
     }
 
     private void touchDownLeftSide() {
@@ -245,7 +253,8 @@
                 .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
 
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
-        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings);
+        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
     }
 
     @Test
@@ -256,7 +265,8 @@
                 .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
 
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
-        verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings);
+        verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
     }
 
     @Test
@@ -267,6 +277,7 @@
                 .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
 
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
-        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings);
+        verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index c751081..24b01e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -16,21 +16,27 @@
 
 package com.android.keyguard;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 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.content.pm.UserInfo;
+import android.content.res.Configuration;
 import android.graphics.Insets;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
@@ -39,17 +45,26 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.UserSwitcherController.UserRecord;
 import com.android.systemui.util.settings.GlobalSettings;
 
 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.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper()
@@ -67,28 +82,43 @@
     private KeyguardSecurityViewFlipper mSecurityViewFlipper;
     @Mock
     private GlobalSettings mGlobalSettings;
+    @Mock
+    private FalsingManager mFalsingManager;
+    @Mock
+    private UserSwitcherController mUserSwitcherController;
+    @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Captor
+    private ArgumentCaptor<FrameLayout.LayoutParams> mLayoutCaptor;
 
     private KeyguardSecurityContainer mKeyguardSecurityContainer;
+    private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams;
 
     @Before
     public void setup() {
         // Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache
         // the real references (rather than the TestableResources that this call creates).
         mContext.ensureTestableResources();
-        FrameLayout.LayoutParams securityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
+                MATCH_PARENT, MATCH_PARENT);
 
         when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
-        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(securityViewFlipperLayoutParams);
+        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
         mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
         mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
         mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+
+        when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
+        when(mUserSwitcherController.getKeyguardStateController())
+                .thenReturn(mKeyguardStateController);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
     }
 
     @Test
     public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
-        mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings);
+        mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
 
         int halfWidthMeasureSpec =
                 View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY);
@@ -99,7 +129,8 @@
 
     @Test
     public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings);
+        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
 
         mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
         verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
@@ -110,7 +141,8 @@
         int imeInsetAmount = 100;
         int systemBarInsetAmount = 10;
 
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings);
+        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -134,7 +166,8 @@
         int imeInsetAmount = 0;
         int systemBarInsetAmount = 10;
 
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings);
+        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -154,7 +187,8 @@
 
     private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
         int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
-        mKeyguardSecurityContainer.initMode(mode, mGlobalSettings);
+        mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController);
 
         mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
         mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH);
@@ -189,4 +223,92 @@
         mKeyguardSecurityContainer.updatePositionByTouchX(1f);
         verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
     }
+
+    @Test
+    public void testUserSwitcherModeViewGravityLandscape() {
+        // GIVEN one user has been setup and in landscape
+        when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
+        Configuration config = new Configuration();
+        config.orientation = Configuration.ORIENTATION_LANDSCAPE;
+        when(getContext().getResources().getConfiguration()).thenReturn(config);
+
+        // WHEN UserSwitcherViewMode is initialized and config has changed
+        setupUserSwitcher();
+        reset(mSecurityViewFlipper);
+        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
+        mKeyguardSecurityContainer.onConfigurationChanged(config);
+
+        // THEN views are oriented side by side
+        verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture());
+        assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.RIGHT | Gravity.BOTTOM);
+        ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
+                R.id.keyguard_bouncer_user_switcher);
+        assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
+                .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+    }
+
+    @Test
+    public void testUserSwitcherModeViewGravityPortrait() {
+        // GIVEN one user has been setup and in landscape
+        when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
+        Configuration config = new Configuration();
+        config.orientation = Configuration.ORIENTATION_PORTRAIT;
+        when(getContext().getResources().getConfiguration()).thenReturn(config);
+
+        // WHEN UserSwitcherViewMode is initialized and config has changed
+        setupUserSwitcher();
+        reset(mSecurityViewFlipper);
+        when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
+        mKeyguardSecurityContainer.onConfigurationChanged(config);
+
+        // THEN views are both centered horizontally
+        verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture());
+        assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+        ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
+                R.id.keyguard_bouncer_user_switcher);
+        assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
+                .isEqualTo(Gravity.CENTER_HORIZONTAL);
+    }
+
+    @Test
+    public void testLessThanTwoUsersDoesNotAllowDropDown() {
+        // GIVEN one user has been setup
+        when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
+
+        // WHEN UserSwitcherViewMode is initialized
+        setupUserSwitcher();
+
+        // THEN the UserSwitcher anchor should not be clickable
+        ViewGroup anchor = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_anchor);
+        assertThat(anchor.isClickable()).isFalse();
+    }
+
+    @Test
+    public void testTwoOrMoreUsersDoesAllowDropDown() {
+        // GIVEN one user has been setup
+        when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(2));
+
+        // WHEN UserSwitcherViewMode is initialized
+        setupUserSwitcher();
+
+        // THEN the UserSwitcher anchor should not be clickable
+        ViewGroup anchor = mKeyguardSecurityContainer.findViewById(R.id.user_switcher_anchor);
+        assertThat(anchor.isClickable()).isTrue();
+    }
+
+    private void setupUserSwitcher() {
+        mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
+                mGlobalSettings, mFalsingManager, mUserSwitcherController);
+    }
+
+    private ArrayList<UserRecord> buildUserRecords(int count) {
+        ArrayList<UserRecord> users = new ArrayList<>();
+        for (int i = 0; i < count; ++i) {
+            UserInfo info = new UserInfo(i /* id */, "Name: " + i, null /* iconPath */,
+                    0 /* flags */);
+            users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
+                    false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */));
+        }
+        return users;
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 9c2382d..80de248 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -26,7 +26,7 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
@@ -63,7 +63,7 @@
     @Mock
     SmartspaceTransitionController mSmartSpaceTransitionController;
     @Mock
-    UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    ScreenOffAnimationController mScreenOffAnimationController;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
 
@@ -84,7 +84,7 @@
                 mDozeParameters,
                 mKeyguardUnlockAnimationController,
                 mSmartSpaceTransitionController,
-                mUnlockedScreenOffAnimationController);
+                mScreenOffAnimationController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index ef9b850..70792cf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -669,7 +669,7 @@
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
 
-        verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt());
+        verify(mLockPatternUtils).requireStrongAuth(anyInt(), anyInt());
     }
 
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardVisibilityHelperTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardVisibilityHelperTest.java
index 1efcbd6..a93a15d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardVisibilityHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardVisibilityHelperTest.java
@@ -26,7 +26,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.communal.CommunalStateController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
@@ -43,7 +43,7 @@
     @Mock
     com.android.systemui.statusbar.phone.DozeParameters mDozeParameters;
     @Mock
-    UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
     ViewPropertyAnimator mViewPropertyAnimator;
     @Mock
@@ -56,7 +56,7 @@
         when(mTargetView.animate()).thenReturn(mViewPropertyAnimator);
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mTargetView,
                 mCommunalStateController, mKeyguardStateController, mDozeParameters,
-                mUnlockedScreenOffAnimationController, false, false);
+                mScreenOffAnimationController, false, false);
     }
 
     @Test
@@ -84,7 +84,7 @@
         // is present.
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mTargetView,
                 mCommunalStateController, mKeyguardStateController, mDozeParameters,
-                mUnlockedScreenOffAnimationController, false, true);
+                mScreenOffAnimationController, false, true);
         mKeyguardVisibilityHelper.setViewVisibility(StatusBarState.KEYGUARD, false,
                 false, StatusBarState.KEYGUARD);
         verify(mTargetView).setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index d819fa2..1fe3d44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -46,7 +46,7 @@
 @RunWithLooper
 class ActivityLaunchAnimatorTest : SysuiTestCase() {
     private val launchContainer = LinearLayout(mContext)
-    private val launchAnimator = LaunchAnimator(mContext, isForTesting = true)
+    private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
     @Mock lateinit var callback: ActivityLaunchAnimator.Callback
     @Spy private val controller = TestLaunchAnimatorController(launchContainer)
     @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index f9ad740..b951345 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -33,7 +33,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class DialogLaunchAnimatorTest : SysuiTestCase() {
-    private val launchAnimator = LaunchAnimator(context, isForTesting = true)
+    private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
     private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
     private val attachedViews = mutableSetOf<View>()
 
@@ -42,7 +42,8 @@
 
     @Before
     fun setUp() {
-        dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager)
+        dialogLaunchAnimator = DialogLaunchAnimator(
+            dreamManager, launchAnimator, isForTesting = true)
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt
new file mode 100644
index 0000000..dadf94e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.animation
+
+/**
+ * A [LaunchAnimator.Timings] to be used in tests.
+ *
+ * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
+ * when computing the progress of a sub-animation (the contents fade in/out).
+ */
+val TEST_TIMINGS = LaunchAnimator.Timings(
+    totalDuration = 0L,
+    contentBeforeFadeOutDelay = 1L,
+    contentBeforeFadeOutDuration = 1L,
+    contentAfterFadeInDelay = 1L,
+    contentAfterFadeInDuration = 1L
+)
+
+/** A [LaunchAnimator.Interpolators] to be used in tests. */
+val TEST_INTERPOLATORS = LaunchAnimator.Interpolators(
+    positionInterpolator = Interpolators.STANDARD,
+    positionXInterpolator = Interpolators.STANDARD,
+    contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
+    contentAfterFadeInInterpolator = Interpolators.STANDARD
+)
\ No newline at end of file
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 241b02d..1dea678 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.StatusBar;
 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.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -169,6 +170,8 @@
     private TypedArray mBrightnessValues;
     @Mock
     private TypedArray mBrightnessBacklight;
+    @Mock
+    private SystemUIDialogManager mSystemUIDialogManager;
 
     // Capture listeners so that they can be used to send events
     @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
@@ -178,8 +181,6 @@
     @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
     private ScreenLifecycle.Observer mScreenObserver;
 
-    @Captor private ArgumentCaptor<UdfpsAnimationViewController> mAnimViewControllerCaptor;
-
     @Before
     public void setUp() {
         setUpResources();
@@ -237,7 +238,8 @@
                 mHandler,
                 mConfigurationController,
                 mSystemClock,
-                mUnlockedScreenOffAnimationController);
+                mUnlockedScreenOffAnimationController,
+                mSystemUIDialogManager);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -641,12 +643,12 @@
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         moveEvent.recycle();
 
-        // THEN low-tick haptic is played
+        // THEN tick haptic is played
         verify(mVibrator).vibrate(
                 anyInt(),
                 anyString(),
                 any(),
-                eq("udfps-onStart-tick"),
+                eq("udfps-onStart-click"),
                 eq(UdfpsController.VIBRATION_ATTRIBUTES));
 
         // THEN make sure vibration attributes has so that it always will play the haptic,
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 1cf21ac..0ae3c39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -42,6 +42,7 @@
 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.phone.panelstate.PanelExpansionListener;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
@@ -92,6 +93,8 @@
     @Mock
     private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     @Mock
+    private SystemUIDialogManager mDialogManager;
+    @Mock
     private UdfpsController mUdfpsController;
     private FakeSystemClock mSystemClock = new FakeSystemClock();
 
@@ -130,6 +133,7 @@
                 mSystemClock,
                 mKeyguardStateController,
                 mUnlockedScreenOffAnimationController,
+                mDialogManager,
                 mUdfpsController);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java
deleted file mode 100644
index 59ddba1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java
+++ /dev/null
@@ -1,178 +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.communal;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-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;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalCondition;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Arrays;
-import java.util.HashSet;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class CommunalConditionsMonitorTest extends SysuiTestCase {
-    private FakeCommunalCondition mCondition1;
-    private FakeCommunalCondition mCondition2;
-    private FakeCommunalCondition mCondition3;
-    private HashSet<CommunalCondition> mConditions;
-
-    private CommunalConditionsMonitor mCommunalConditionsMonitor;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mCondition1 = spy(new FakeCommunalCondition());
-        mCondition2 = spy(new FakeCommunalCondition());
-        mCondition3 = spy(new FakeCommunalCondition());
-        mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
-
-        mCommunalConditionsMonitor = new CommunalConditionsMonitor(mConditions);
-    }
-
-    @Test
-    public void addCallback_addFirstCallback_addCallbackToAllConditions() {
-        final CommunalConditionsMonitor.Callback callback1 =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback1);
-        mConditions.forEach(condition -> verify(condition).addCallback(any()));
-
-        final CommunalConditionsMonitor.Callback callback2 =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback2);
-        mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
-    }
-
-    @Test
-    public void addCallback_addFirstCallback_reportWithDefaultValue() {
-        final CommunalConditionsMonitor.Callback callback =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback);
-        verify(callback).onConditionsChanged(false);
-    }
-
-    @Test
-    public void addCallback_addSecondCallback_reportWithExistingValue() {
-        final CommunalConditionsMonitor.Callback callback1 =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback1);
-
-        mCommunalConditionsMonitor.overrideAllConditionsMet(true);
-
-        final CommunalConditionsMonitor.Callback callback2 =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback2);
-        verify(callback2).onConditionsChanged(true);
-    }
-
-    @Test
-    public void removeCallback_shouldNoLongerReceiveUpdate() {
-        final CommunalConditionsMonitor.Callback callback =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback);
-        clearInvocations(callback);
-        mCommunalConditionsMonitor.removeCallback(callback);
-
-        mCommunalConditionsMonitor.overrideAllConditionsMet(true);
-        verify(callback, never()).onConditionsChanged(true);
-
-        mCommunalConditionsMonitor.overrideAllConditionsMet(false);
-        verify(callback, never()).onConditionsChanged(false);
-    }
-
-    @Test
-    public void removeCallback_removeLastCallback_removeCallbackFromAllConditions() {
-        final CommunalConditionsMonitor.Callback callback1 =
-                mock(CommunalConditionsMonitor.Callback.class);
-        final CommunalConditionsMonitor.Callback callback2 =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback1);
-        mCommunalConditionsMonitor.addCallback(callback2);
-
-        mCommunalConditionsMonitor.removeCallback(callback1);
-        mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
-
-        mCommunalConditionsMonitor.removeCallback(callback2);
-        mConditions.forEach(condition -> verify(condition).removeCallback(any()));
-    }
-
-    @Test
-    public void updateCallbacks_allConditionsMet_reportTrue() {
-        final CommunalConditionsMonitor.Callback callback =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback);
-        clearInvocations(callback);
-
-        mCondition1.fakeUpdateCondition(true);
-        mCondition2.fakeUpdateCondition(true);
-        mCondition3.fakeUpdateCondition(true);
-
-        verify(callback).onConditionsChanged(true);
-    }
-
-    @Test
-    public void updateCallbacks_oneConditionStoppedMeeting_reportFalse() {
-        final CommunalConditionsMonitor.Callback callback =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback);
-
-        mCondition1.fakeUpdateCondition(true);
-        mCondition2.fakeUpdateCondition(true);
-        mCondition3.fakeUpdateCondition(true);
-        clearInvocations(callback);
-
-        mCondition1.fakeUpdateCondition(false);
-        verify(callback).onConditionsChanged(false);
-    }
-
-    @Test
-    public void updateCallbacks_shouldOnlyUpdateWhenValueChanges() {
-        final CommunalConditionsMonitor.Callback callback =
-                mock(CommunalConditionsMonitor.Callback.class);
-        mCommunalConditionsMonitor.addCallback(callback);
-        verify(callback).onConditionsChanged(false);
-        clearInvocations(callback);
-
-        mCondition1.fakeUpdateCondition(true);
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-
-        mCondition2.fakeUpdateCondition(true);
-        verify(callback, never()).onConditionsChanged(anyBoolean());
-
-        mCondition3.fakeUpdateCondition(true);
-        verify(callback).onConditionsChanged(true);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java
index 6cfa40a..8bd5e79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java
@@ -34,7 +34,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -71,7 +71,7 @@
     private DozeParameters mDozeParameters;
 
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private ScreenOffAnimationController mScreenOffAnimationController;
 
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
@@ -93,7 +93,7 @@
 
         mController = new CommunalHostViewController(mFakeExecutor, mCommunalStateController,
                 mKeyguardUpdateMonitor, mKeyguardStateController, mDozeParameters,
-                mUnlockedScreenOffAnimationController, mStatusBarStateController, mCommunalView);
+                mScreenOffAnimationController, mStatusBarStateController, mCommunalView);
         mController.init();
         mFakeExecutor.runAllReady();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
index fb8efa9..4a29ada 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalManagerUpdaterTest.java
@@ -16,17 +16,21 @@
 
 package com.android.systemui.communal;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.app.communal.CommunalManager;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.condition.Monitor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -36,27 +40,36 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
 public class CommunalManagerUpdaterTest extends SysuiTestCase {
     private CommunalSourceMonitor mMonitor;
     @Mock
     private CommunalManager mCommunalManager;
     @Mock
-    private CommunalConditionsMonitor mCommunalConditionsMonitor;
+    private Monitor mCommunalConditionsMonitor;
+
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(CommunalManager.class, mCommunalManager);
 
-        mMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
+        doAnswer(invocation -> {
+            final Monitor.Callback callback = invocation.getArgument(0);
+            callback.onConditionsChanged(true);
+            return null;
+        }).when(mCommunalConditionsMonitor).addCallback(any());
+
+        mMonitor = new CommunalSourceMonitor(mExecutor, mCommunalConditionsMonitor);
         final CommunalManagerUpdater updater = new CommunalManagerUpdater(mContext, mMonitor);
         updater.start();
+        clearInvocations(mCommunalManager);
     }
 
     @Test
     public void testUpdateSystemService_false() {
         mMonitor.setSource(null);
+        mExecutor.runAllReady();
         verify(mCommunalManager).setCommunalViewShowing(false);
     }
 
@@ -64,6 +77,7 @@
     public void testUpdateSystemService_true() {
         final CommunalSource source = mock(CommunalSource.class);
         mMonitor.setSource(source);
+        mExecutor.runAllReady();
         verify(mCommunalManager).setCommunalViewShowing(true);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
index cf147f0..2d52c42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java
@@ -31,8 +31,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalCondition;
 import com.android.systemui.communal.conditions.CommunalSettingCondition;
+import com.android.systemui.util.condition.Condition;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.utils.os.FakeHandler;
 
@@ -57,7 +57,7 @@
     public void addCallback_communalSettingEnabled_immediatelyReportsTrue() {
         updateCommunalSetting(true);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         verify(callback).onConditionChanged(mCondition, true);
     }
@@ -66,7 +66,7 @@
     public void addCallback_communalSettingDisabled_noReport() {
         updateCommunalSetting(false);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean());
     }
@@ -75,7 +75,7 @@
     public void updateCallback_communalSettingEnabled_reportsTrue() {
         updateCommunalSetting(false);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         clearInvocations(callback);
 
@@ -87,7 +87,7 @@
     public void updateCallback_communalSettingDisabled_reportsFalse() {
         updateCommunalSetting(true);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         clearInvocations(callback);
 
@@ -99,7 +99,7 @@
     public void updateCallback_communalSettingDidNotChange_neverReportDup() {
         updateCommunalSetting(true);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         clearInvocations(callback);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
index df9a2cb..df1cc76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java
@@ -31,7 +31,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalConditionsMonitor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.condition.Monitor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,16 +49,17 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class CommunalSourceMonitorTest extends SysuiTestCase {
-    @Mock private CommunalConditionsMonitor mCommunalConditionsMonitor;
+    @Mock private Monitor mCommunalConditionsMonitor;
 
-    @Captor private ArgumentCaptor<CommunalConditionsMonitor.Callback> mConditionsCallbackCaptor;
+    @Captor private ArgumentCaptor<Monitor.Callback> mConditionsCallbackCaptor;
 
     private CommunalSourceMonitor mCommunalSourceMonitor;
+    private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mCommunalSourceMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor);
+        mCommunalSourceMonitor = new CommunalSourceMonitor(mExecutor, mCommunalConditionsMonitor);
     }
 
     @Test
@@ -64,7 +67,7 @@
         final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class);
         final CommunalSource source = mock(CommunalSource.class);
 
-        mCommunalSourceMonitor.setSource(source);
+        setSource(source);
         mCommunalSourceMonitor.addCallback(callback);
         setConditionsMet(true);
 
@@ -78,7 +81,7 @@
 
         mCommunalSourceMonitor.addCallback(callback);
         mCommunalSourceMonitor.removeCallback(callback);
-        mCommunalSourceMonitor.setSource(source);
+        setSource(source);
 
         verify(callback, never()).onSourceAvailable(any());
     }
@@ -91,7 +94,7 @@
         mCommunalSourceMonitor.addCallback(callback);
         setConditionsMet(true);
         clearInvocations(callback);
-        mCommunalSourceMonitor.setSource(source);
+        setSource(source);
 
         verifyOnSourceAvailableCalledWith(callback, source);
     }
@@ -103,7 +106,7 @@
 
         mCommunalSourceMonitor.addCallback(callback);
         setConditionsMet(false);
-        mCommunalSourceMonitor.setSource(source);
+        setSource(source);
 
         verify(callback, never()).onSourceAvailable(any());
     }
@@ -114,7 +117,7 @@
         final CommunalSource source = mock(CommunalSource.class);
 
         mCommunalSourceMonitor.addCallback(callback);
-        mCommunalSourceMonitor.setSource(source);
+        setSource(source);
 
         // The callback should not have executed since communal is disabled.
         verify(callback, never()).onSourceAvailable(any());
@@ -130,7 +133,7 @@
         final CommunalSource source = mock(CommunalSource.class);
 
         mCommunalSourceMonitor.addCallback(callback);
-        mCommunalSourceMonitor.setSource(source);
+        setSource(source);
         setConditionsMet(true);
         verifyOnSourceAvailableCalledWith(callback, source);
 
@@ -151,9 +154,16 @@
     // Pushes an update on whether the communal conditions are met, assuming that a callback has
     // been registered with the communal conditions monitor.
     private void setConditionsMet(boolean value) {
+        mExecutor.runAllReady();
         verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture());
-        final CommunalConditionsMonitor.Callback conditionsCallback =
+        final Monitor.Callback conditionsCallback =
                 mConditionsCallbackCaptor.getValue();
         conditionsCallback.onConditionsChanged(value);
+        mExecutor.runAllReady();
+    }
+
+    private void setSource(CommunalSource source) {
+        mCommunalSourceMonitor.setSource(source);
+        mExecutor.runAllReady();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java
index 2ed38a4..1d9a059 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourcePrimerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -26,14 +27,16 @@
 import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 
-import androidx.concurrent.futures.CallbackToFutureAdapter;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;;
+import com.android.systemui.util.ref.GcWeakReference;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import com.google.common.util.concurrent.ListenableFuture;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,6 +55,35 @@
     private static final int RETRY_DELAY_MS = 1000;
     private static final int CONNECTION_MIN_DURATION_MS = 5000;
 
+    // A simple implementation of {@link CommunalSource.Observer} to capture a callback value.
+    // Used to ensure the references to a {@link CommunalSource.Observer.Callback} can be fully
+    // removed.
+    private static class FakeObserver implements CommunalSource.Observer {
+        public GcWeakReference<Callback> mLastCallback;
+
+        @Override
+        public void addCallback(Callback callback) {
+            mLastCallback = new GcWeakReference<>(callback);
+        }
+
+        @Override
+        public void removeCallback(Callback callback) {
+            if (mLastCallback.get() == callback) {
+                mLastCallback = null;
+            }
+        }
+    }
+
+    // A simple implementation of {@link CommunalSource} to capture callback values. This
+    // implementation better emulates the {@link WeakReference} wrapping behavior of
+    // {@link CommunalSource} implementations than a mock.
+    private static class FakeSource implements CommunalSource {
+        @Override
+        public ListenableFuture<CommunalViewResult> requestCommunalView(Context context) {
+            return null;
+        }
+    }
+
     @Mock
     private Context mContext;
 
@@ -61,8 +93,7 @@
     private FakeSystemClock mFakeClock = new FakeSystemClock();
     private FakeExecutor mFakeExecutor = new FakeExecutor(mFakeClock);
 
-    @Mock
-    private CommunalSource mSource;
+    private FakeSource mSource = new FakeSource();
 
     @Mock
     private CommunalSourceMonitor mCommunalSourceMonitor;
@@ -71,7 +102,9 @@
     private CommunalSource.Connector mConnector;
 
     @Mock
-    private CommunalSource.Observer mObserver;
+    private CommunalSource.Connection mConnection;
+
+    private FakeObserver mObserver = new FakeObserver();
 
     private CommunalSourcePrimer mPrimer;
 
@@ -93,35 +126,35 @@
                 mCommunalSourceMonitor, Optional.of(mConnector), Optional.of(mObserver));
     }
 
+    private CommunalSource.Connection.Callback captureCallbackAndSend(
+            CommunalSource.Connector connector, Optional<CommunalSource> source) {
+        ArgumentCaptor<CommunalSource.Connection.Callback> connectionCallback =
+                ArgumentCaptor.forClass(CommunalSource.Connection.Callback.class);
+
+        verify(connector).connect(connectionCallback.capture());
+        Mockito.clearInvocations(connector);
+
+        final CommunalSource.Connection.Callback callback = connectionCallback.getValue();
+        callback.onSourceEstablished(source);
+
+        return callback;
+    }
+
     @Test
     public void testConnect() {
-        when(mConnector.connect()).thenReturn(
-                CallbackToFutureAdapter.getFuture(completer -> {
-                    completer.set(Optional.of(mSource));
-                    return "test";
-                }));
-
         mPrimer.onBootCompleted();
-        mFakeExecutor.runAllReady();
+        captureCallbackAndSend(mConnector, Optional.of(mSource));
         verify(mCommunalSourceMonitor).setSource(mSource);
     }
 
     @Test
     public void testRetryOnBindFailure() throws Exception {
-        when(mConnector.connect()).thenReturn(
-                CallbackToFutureAdapter.getFuture(completer -> {
-                    completer.set(Optional.empty());
-                    return "test";
-                }));
-
         mPrimer.onBootCompleted();
-        mFakeExecutor.runAllReady();
 
         // Verify attempts happen. Note that we account for the retries plus initial attempt, which
         // is not scheduled.
         for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
-            verify(mConnector, times(1)).connect();
-            clearInvocations(mConnector);
+            captureCallbackAndSend(mConnector, Optional.empty());
             mFakeExecutor.advanceClockToNext();
             mFakeExecutor.runAllReady();
         }
@@ -131,76 +164,42 @@
 
     @Test
     public void testRetryOnDisconnectFailure() throws Exception {
-        when(mConnector.connect()).thenReturn(
-                CallbackToFutureAdapter.getFuture(completer -> {
-                    completer.set(Optional.of(mSource));
-                    return "test";
-                }));
-
         mPrimer.onBootCompleted();
-        mFakeExecutor.runAllReady();
-
         // Verify attempts happen. Note that we account for the retries plus initial attempt, which
         // is not scheduled.
         for (int attemptCount = 0; attemptCount < MAX_RETRIES + 1; attemptCount++) {
-            verify(mConnector, times(1)).connect();
-            clearInvocations(mConnector);
-            ArgumentCaptor<CommunalSource.Callback> callbackCaptor =
-                    ArgumentCaptor.forClass(CommunalSource.Callback.class);
-            verify(mSource).addCallback(callbackCaptor.capture());
-            clearInvocations(mSource);
+            final CommunalSource.Connection.Callback callback =
+                    captureCallbackAndSend(mConnector, Optional.of(mSource));
             verify(mCommunalSourceMonitor).setSource(Mockito.notNull());
             clearInvocations(mCommunalSourceMonitor);
-            callbackCaptor.getValue().onDisconnected();
+            callback.onDisconnected();
             mFakeExecutor.advanceClockToNext();
             mFakeExecutor.runAllReady();
         }
 
-        verify(mConnector, never()).connect();
+        verify(mConnector, never()).connect(any());
     }
 
     @Test
     public void testAttemptOnPackageChange() {
-        when(mConnector.connect()).thenReturn(
-                CallbackToFutureAdapter.getFuture(completer -> {
-                    completer.set(Optional.empty());
-                    return "test";
-                }));
-
         mPrimer.onBootCompleted();
-        mFakeExecutor.runAllReady();
+        captureCallbackAndSend(mConnector, Optional.empty());
 
-        final ArgumentCaptor<CommunalSource.Observer.Callback> callbackCaptor =
-                ArgumentCaptor.forClass(CommunalSource.Observer.Callback.class);
-        verify(mObserver).addCallback(callbackCaptor.capture());
+        mObserver.mLastCallback.get().onSourceChanged();
 
-        clearInvocations(mConnector);
-        callbackCaptor.getValue().onSourceChanged();
-
-        verify(mConnector, times(1)).connect();
+        verify(mConnector, times(1)).connect(any());
     }
 
     @Test
     public void testDisconnect() {
-        final ArgumentCaptor<CommunalSource.Callback> callbackCaptor =
-                ArgumentCaptor.forClass(CommunalSource.Callback.class);
-
-        when(mConnector.connect()).thenReturn(
-                CallbackToFutureAdapter.getFuture(completer -> {
-                    completer.set(Optional.of(mSource));
-                    return "test";
-                }));
-
         mPrimer.onBootCompleted();
-        mFakeExecutor.runAllReady();
+        final CommunalSource.Connection.Callback callback =
+                captureCallbackAndSend(mConnector, Optional.of(mSource));
         verify(mCommunalSourceMonitor).setSource(mSource);
-        verify(mSource).addCallback(callbackCaptor.capture());
 
-        clearInvocations(mConnector);
         mFakeClock.advanceTime(CONNECTION_MIN_DURATION_MS + 1);
-        callbackCaptor.getValue().onDisconnected();
-        mFakeExecutor.runAllReady();
+        callback.onDisconnected();
 
-        verify(mConnector).connect();
+        verify(mConnector).connect(any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java
new file mode 100644
index 0000000..1cd6069
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.communal;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+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.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class PackageObserverTest extends SysuiTestCase {
+    private static final String PACKAGE_NAME = "com.foo.bar";
+
+    @Mock
+    Context mContext;
+
+    @Mock
+    CommunalSource.Observer.Callback mCallback;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testChange() {
+        final PackageObserver observer = new PackageObserver(mContext, PACKAGE_NAME);
+        final ArgumentCaptor<BroadcastReceiver> receiverCapture =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        observer.addCallback(mCallback);
+
+        // Verify broadcast receiver registered.
+        verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());
+
+        // Simulate package change.
+        receiverCapture.getValue().onReceive(mContext, new Intent());
+
+        // Check that callback was informed.
+        verify(mCallback).onSourceChanged();
+
+        observer.removeCallback(mCallback);
+
+        // Make sure receiver is unregistered on last callback removal
+        verify(mContext).unregisterReceiver(receiverCapture.getValue());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 5b472ba..badafa4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -27,7 +27,6 @@
 import static com.android.systemui.doze.DozeMachine.State.FINISH;
 import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
 import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
@@ -51,7 +50,6 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
@@ -94,8 +92,6 @@
     DevicePostureController mDevicePostureController;
     @Mock
     DozeLog mDozeLog;
-    @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
 
@@ -133,8 +129,7 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog,
-                mUnlockedScreenOffAnimationController);
+                mDozeLog);
     }
 
     @Test
@@ -240,8 +235,7 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog,
-                mUnlockedScreenOffAnimationController);
+                mDozeLog);
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
         reset(mDozeHost);
@@ -278,8 +272,7 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog,
-                mUnlockedScreenOffAnimationController);
+                mDozeLog);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE_AOD);
@@ -310,8 +303,7 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog,
-                mUnlockedScreenOffAnimationController);
+                mDozeLog);
 
         // GIVEN the device is in AOD
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -349,8 +341,7 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog,
-                mUnlockedScreenOffAnimationController);
+                mDozeLog);
 
         // GIVEN device is in AOD
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
@@ -392,8 +383,7 @@
                 mWakefulnessLifecycle,
                 mDozeParameters,
                 mDevicePostureController,
-                mDozeLog,
-                mUnlockedScreenOffAnimationController);
+                mDozeLog);
         verify(mDevicePostureController).addCallback(postureCallbackCaptor.capture());
 
         // GIVEN device is in AOD
@@ -475,11 +465,11 @@
     }
 
     @Test
-    public void transitionToDoze_duringUnlockedScreenOff_afterTimeout_clampsToDim() {
+    public void transitionToDoze_shouldClampBrightness_afterTimeout_clampsToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
-        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
+        when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(true);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
@@ -490,11 +480,11 @@
     }
 
     @Test
-    public void transitionToDoze_duringUnlockedScreenOff_notAfterTimeout_doesNotClampToDim() {
+    public void transitionToDoze_shouldClampBrightness_notAfterTimeout_doesNotClampToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
-        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
+        when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(true);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
@@ -505,11 +495,11 @@
     }
 
     @Test
-    public void transitionToDoze_duringUnlockedScreenOff_afterTimeout_noScreenOff_doesNotClampToDim() {
+    public void transitionToDoze_noClampBrightness_afterTimeout_noScreenOff_doesNotClampToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
-        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
+        when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
@@ -519,13 +509,13 @@
     }
 
     @Test
-    public void transitionToDoze_duringLockedScreenOff_afterTimeout_clampsToDim() {
+    public void transitionToDoze_noClampBrightness_afterTimeout_clampsToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         when(mWakefulnessLifecycle.getWakefulness()).thenReturn(
                 WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
-        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
+        when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
@@ -534,13 +524,13 @@
     }
 
     @Test
-    public void transitionToDoze_duringLockedScreenOff_notAfterTimeout_doesNotClampToDim() {
+    public void transitionToDoze_noClampBrigthness_notAfterTimeout_doesNotClampToDim() {
         when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
         when(mWakefulnessLifecycle.getWakefulness()).thenReturn(
                 WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP);
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
-        when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
+        when(mDozeParameters.shouldClampToDimBrightness()).thenReturn(false);
 
         mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
         mScreen.transitionTo(INITIALIZED, DOZE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 1d34aac..55af51d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -45,6 +45,8 @@
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
+import com.android.systemui.unfold.FoldAodAnimationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.After;
@@ -54,6 +56,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class DozeUiTest extends SysuiTestCase {
@@ -79,6 +83,10 @@
     @Mock
     private StatusBarStateController mStatusBarStateController;
     @Mock
+    private FoldAodAnimationController mFoldAodAnimationController;
+    @Mock
+    private SysUIUnfoldComponent mSysUIUnfoldComponent;
+    @Mock
     private ConfigurationController mConfigurationController;
 
     @Before
@@ -90,9 +98,13 @@
         mWakeLock = new WakeLockFake();
         mHandler = mHandlerThread.getThreadHandler();
 
+        when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+                .thenReturn(mFoldAodAnimationController);
+
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
                 mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
-                () -> mStatusBarStateController, mConfigurationController);
+                mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
+                mConfigurationController);
         mDozeUi.setDozeMachine(mMachine);
     }
 
@@ -121,6 +133,7 @@
         reset(mHost);
         when(mDozeParameters.getAlwaysOn()).thenReturn(false);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
 
         mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(false);
         verify(mHost).setAnimateScreenOff(eq(false));
@@ -131,6 +144,7 @@
         reset(mHost);
         when(mDozeParameters.getAlwaysOn()).thenReturn(true);
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
 
         // Take over when the keyguard is visible.
         mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
@@ -142,6 +156,18 @@
     }
 
     @Test
+    public void propagatesAnimateScreenOff_alwaysOn_shouldAnimateDozingChangeIsFalse() {
+        reset(mHost);
+        when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+        when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(false);
+
+        // Take over when the keyguard is visible.
+        mDozeUi.getKeyguardCallback().onKeyguardVisibilityChanged(true);
+        verify(mHost).setAnimateScreenOff(eq(false));
+    }
+
+    @Test
     public void neverAnimateScreenOff_whenNotSupported() {
         // Re-initialize DozeParameters saying that the display requires blanking.
         reset(mDozeParameters);
@@ -149,7 +175,8 @@
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
                 mDozeParameters, mKeyguardUpdateMonitor, mDozeLog, mTunerService,
-                () -> mStatusBarStateController, mConfigurationController);
+                mStatusBarStateController, Optional.of(mSysUIUnfoldComponent),
+                mConfigurationController);
         mDozeUi.setDozeMachine(mMachine);
 
         // Never animate if display doesn't support it.
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 53bfeee..904ee91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -37,6 +37,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
@@ -78,16 +79,41 @@
     @Mock
     DreamOverlayStateController mDreamOverlayStateController;
 
+    @Mock
+    DreamOverlayComponent.Factory mDreamOverlayStatusBarViewComponentFactory;
+
+    @Mock
+    DreamOverlayComponent mDreamOverlayComponent;
+
+    @Mock
+    DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
+
+    @Mock
+    DreamOverlayContainerView mDreamOverlayContainerView;
+
+    @Mock
+    ViewGroup mDreamOverlayContentView;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(WindowManager.class, mWindowManager);
+
+        when(mDreamOverlayComponent.getDreamOverlayContentView())
+                .thenReturn(mDreamOverlayContentView);
+        when(mDreamOverlayComponent.getDreamOverlayContainerView())
+                .thenReturn(mDreamOverlayContainerView);
+        when(mDreamOverlayComponent.getDreamOverlayStatusBarViewController())
+                .thenReturn(mDreamOverlayStatusBarViewController);
+        when(mDreamOverlayStatusBarViewComponentFactory.create())
+                .thenReturn(mDreamOverlayComponent);
+
     }
 
     @Test
     public void testInteraction() throws Exception {
         final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
-                mDreamOverlayStateController);
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
         final IBinder proxy = service.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
         clearInvocations(mWindowManager);
@@ -128,7 +154,7 @@
     @Test
     public void testListening() throws Exception {
         final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
-                mDreamOverlayStateController);
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
 
         final IBinder proxy = service.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
@@ -150,4 +176,35 @@
         // Verify provider is asked to create overlay.
         verify(mProvider).onCreateOverlay(any(), any(), any());
     }
+
+    @Test
+    public void testDreamOverlayStatusBarViewControllerInitialized() throws Exception {
+        final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
+
+        final IBinder proxy = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mDreamOverlayStatusBarViewController).init();
+    }
+
+    @Test
+    public void testRootViewAttachListenerIsAddedToDreamOverlayContentView() throws Exception {
+        final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+                mDreamOverlayStateController, mDreamOverlayStatusBarViewComponentFactory);
+
+        final IBinder proxy = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        verify(mDreamOverlayContentView).addOnAttachStateChangeListener(
+                any(View.OnAttachStateChangeListener.class));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
new file mode 100644
index 0000000..7f72dda
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+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;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
+
+    @Mock
+    DreamOverlayStatusBarView mView;
+    @Mock
+    BatteryController mBatteryController;
+    @Mock
+    BatteryMeterViewController mBatteryMeterViewController;
+    @Mock
+    ConnectivityManager mConnectivityManager;
+    @Mock
+    NetworkCapabilities mNetworkCapabilities;
+    @Mock
+    Network mNetwork;
+
+    DreamOverlayStatusBarViewController mController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mController = new DreamOverlayStatusBarViewController(
+                mContext, mView, mBatteryController, mBatteryMeterViewController,
+                mConnectivityManager);
+    }
+
+    @Test
+    public void testOnInitInitializesControllers() {
+        mController.onInit();
+        verify(mBatteryMeterViewController).init();
+    }
+
+    @Test
+    public void testOnViewAttachedAddsBatteryControllerCallback() {
+        mController.onViewAttached();
+        verify(mBatteryController)
+                .addCallback(any(BatteryController.BatteryStateChangeCallback.class));
+    }
+
+    @Test
+    public void testOnViewAttachedRegistersNetworkCallback() {
+        mController.onViewAttached();
+        verify(mConnectivityManager)
+                .registerNetworkCallback(any(NetworkRequest.class), any(
+                        ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testOnViewAttachedShowsWifiStatusWhenWifiUnavailable() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(false);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+        verify(mView).showWifiStatus(true);
+    }
+
+    @Test
+    public void testOnViewAttachedHidesWifiStatusWhenWifiAvailable() {
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(true);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+        verify(mView).showWifiStatus(false);
+    }
+
+    @Test
+    public void testOnViewAttachedShowsWifiStatusWhenNetworkCapabilitiesUnavailable() {
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(null);
+        mController.onViewAttached();
+        verify(mView).showWifiStatus(true);
+    }
+
+    @Test
+    public void testOnViewDetachedRemovesBatteryControllerCallback() {
+        mController.onViewDetached();
+        verify(mBatteryController)
+                .removeCallback(any(BatteryController.BatteryStateChangeCallback.class));
+    }
+
+    @Test
+    public void testOnViewDetachedUnregistersNetworkCallback() {
+        mController.onViewDetached();
+        verify(mConnectivityManager)
+                .unregisterNetworkCallback(any(ConnectivityManager.NetworkCallback.class));
+    }
+
+    @Test
+    public void testBatteryPercentTextShownWhenBatteryLevelChangesWhileCharging() {
+        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
+                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
+        mController.onViewAttached();
+        verify(mBatteryController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onBatteryLevelChanged(1, true, true);
+        verify(mView).showBatteryPercentText(true);
+    }
+
+    @Test
+    public void testBatteryPercentTextHiddenWhenBatteryLevelChangesWhileNotCharging() {
+        final ArgumentCaptor<BatteryController.BatteryStateChangeCallback> callbackCapture =
+                ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback.class);
+        mController.onViewAttached();
+        verify(mBatteryController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onBatteryLevelChanged(1, true, false);
+        verify(mView).showBatteryPercentText(false);
+    }
+
+    @Test
+    public void testWifiStatusHiddenWhenWifiBecomesAvailable() {
+        // Make sure wifi starts out unavailable when onViewAttached is called.
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(false);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+        callbackCapture.getValue().onAvailable(mNetwork);
+        verify(mView).showWifiStatus(false);
+    }
+
+    @Test
+    public void testWifiStatusShownWhenWifiBecomesUnavailable() {
+        // Make sure wifi starts out available when onViewAttached is called.
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(true);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+        callbackCapture.getValue().onLost(mNetwork);
+        verify(mView).showWifiStatus(true);
+    }
+
+    @Test
+    public void testWifiStatusHiddenWhenCapabilitiesChange() {
+        // Make sure wifi starts out unavailable when onViewAttached is called.
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(false);
+        when(mConnectivityManager.getNetworkCapabilities(any())).thenReturn(mNetworkCapabilities);
+        mController.onViewAttached();
+
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackCapture =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mConnectivityManager).registerNetworkCallback(any(), callbackCapture.capture());
+        when(mNetworkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI))
+                .thenReturn(true);
+        callbackCapture.getValue().onCapabilitiesChanged(mNetwork, mNetworkCapabilities);
+        verify(mView).showWifiStatus(false);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
new file mode 100644
index 0000000..cb16bec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -0,0 +1,320 @@
+/*
+ * 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.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+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.withArgCaptor
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+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-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+class FeatureFlagsDebugTest : SysuiTestCase() {
+    private lateinit var mFeatureFlagsDebug: FeatureFlagsDebug
+
+    @Mock private lateinit var mFlagManager: FlagManager
+    @Mock private lateinit var mMockContext: Context
+    @Mock private lateinit var mSecureSettings: SecureSettings
+    @Mock private lateinit var mResources: Resources
+    @Mock private lateinit var mDumpManager: DumpManager
+    private val mFlagMap = mutableMapOf<Int, Flag<*>>()
+    private lateinit var mBroadcastReceiver: BroadcastReceiver
+    private lateinit var mClearCacheAction: Consumer<Int>
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFeatureFlagsDebug = FeatureFlagsDebug(
+            mFlagManager,
+            mMockContext,
+            mSecureSettings,
+            mResources,
+            mDumpManager,
+            { mFlagMap }
+        )
+        verify(mFlagManager).restartAction = any()
+        mBroadcastReceiver = withArgCaptor {
+            verify(mMockContext).registerReceiver(capture(), any(), nullable(), nullable())
+        }
+        mClearCacheAction = withArgCaptor {
+            verify(mFlagManager).clearCacheAction = capture()
+        }
+        whenever(mFlagManager.idToSettingsKey(any())).thenAnswer { "key-${it.arguments[0]}" }
+    }
+
+    @Test
+    fun testReadBooleanFlag() {
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(4), any())).thenReturn(false)
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(1, false))).isFalse()
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(2, true))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(3, false))).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(BooleanFlag(4, true))).isFalse()
+    }
+
+    @Test
+    fun testReadResourceBooleanFlag() {
+        whenever(mResources.getBoolean(1001)).thenReturn(false)
+        whenever(mResources.getBoolean(1002)).thenReturn(true)
+        whenever(mResources.getBoolean(1003)).thenReturn(false)
+        whenever(mResources.getBoolean(1004)).thenAnswer { throw NameNotFoundException() }
+        whenever(mResources.getBoolean(1005)).thenAnswer { throw NameNotFoundException() }
+
+        whenever(mFlagManager.readFlagValue<Boolean>(eq(3), any())).thenReturn(true)
+        whenever(mFlagManager.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()
+
+        Assert.assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsDebug.isEnabled(ResourceBooleanFlag(4, 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))
+        }
+    }
+
+    @Test
+    fun testReadStringFlag() {
+        whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
+        whenever(mFlagManager.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")
+    }
+
+    @Test
+    fun testReadResourceStringFlag() {
+        whenever(mResources.getString(1001)).thenReturn("")
+        whenever(mResources.getString(1002)).thenReturn("resource2")
+        whenever(mResources.getString(1003)).thenReturn("resource3")
+        whenever(mResources.getString(1004)).thenReturn(null)
+        whenever(mResources.getString(1005)).thenAnswer { throw NameNotFoundException() }
+        whenever(mResources.getString(1006)).thenAnswer { throw NameNotFoundException() }
+
+        whenever(mFlagManager.readFlagValue<String>(eq(3), any())).thenReturn("override3")
+        whenever(mFlagManager.readFlagValue<String>(eq(4), any())).thenReturn("override4")
+        whenever(mFlagManager.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")
+
+        Assert.assertThrows(NullPointerException::class.java) {
+            mFeatureFlagsDebug.getString(ResourceStringFlag(4, 1004))
+        }
+        Assert.assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsDebug.getString(ResourceStringFlag(5, 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))
+        }
+    }
+
+    @Test
+    fun testBroadcastReceiverIgnoresInvalidData() {
+        addFlag(BooleanFlag(1, false))
+        addFlag(ResourceBooleanFlag(2, 1002))
+        addFlag(StringFlag(3, "flag3"))
+        addFlag(ResourceStringFlag(4, 1004))
+
+        mBroadcastReceiver.onReceive(mMockContext, null)
+        mBroadcastReceiver.onReceive(mMockContext, Intent())
+        mBroadcastReceiver.onReceive(mMockContext, Intent("invalid action"))
+        mBroadcastReceiver.onReceive(mMockContext, Intent(FlagManager.ACTION_SET_FLAG))
+        setByBroadcast(0, false)     // unknown id does nothing
+        setByBroadcast(1, "string")  // wrong type does nothing
+        setByBroadcast(2, 123)       // wrong type does nothing
+        setByBroadcast(3, false)     // wrong type does nothing
+        setByBroadcast(4, 123)       // wrong type does nothing
+        verifyNoMoreInteractions(mFlagManager, mSecureSettings)
+    }
+
+    @Test
+    fun testIntentWithIdButNoValueKeyClears() {
+        addFlag(BooleanFlag(1, false))
+
+        // trying to erase an id not in the map does noting
+        mBroadcastReceiver.onReceive(
+            mMockContext,
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 0)
+        )
+        verifyNoMoreInteractions(mFlagManager, mSecureSettings)
+
+        // valid id with no value puts empty string in the setting
+        mBroadcastReceiver.onReceive(
+            mMockContext,
+            Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_ID, 1)
+        )
+        verifyPutData(1, "", numReads = 0)
+    }
+
+    @Test
+    fun testSetBooleanFlag() {
+        addFlag(BooleanFlag(1, false))
+        addFlag(BooleanFlag(2, false))
+        addFlag(ResourceBooleanFlag(3, 1003))
+        addFlag(ResourceBooleanFlag(4, 1004))
+
+        setByBroadcast(1, false)
+        verifyPutData(1, "{\"type\":\"boolean\",\"value\":false}")
+
+        setByBroadcast(2, true)
+        verifyPutData(2, "{\"type\":\"boolean\",\"value\":true}")
+
+        setByBroadcast(3, false)
+        verifyPutData(3, "{\"type\":\"boolean\",\"value\":false}")
+
+        setByBroadcast(4, true)
+        verifyPutData(4, "{\"type\":\"boolean\",\"value\":true}")
+    }
+
+    @Test
+    fun testSetStringFlag() {
+        addFlag(StringFlag(1, "flag1"))
+        addFlag(ResourceStringFlag(2, 1002))
+
+        setByBroadcast(1, "override1")
+        verifyPutData(1, "{\"type\":\"string\",\"value\":\"override1\"}")
+
+        setByBroadcast(2, "override2")
+        verifyPutData(2, "{\"type\":\"string\",\"value\":\"override2\"}")
+    }
+
+    @Test
+    fun testSetFlagClearsCache() {
+        val flag1 = addFlag(StringFlag(1, "flag1"))
+        whenever(mFlagManager.readFlagValue<String>(eq(1), any())).thenReturn("original")
+
+        // gets the flag & cache it
+        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+        verify(mFlagManager).readFlagValue(eq(1), eq(StringFlagSerializer))
+
+        // hit the cache
+        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("original")
+        verifyNoMoreInteractions(mFlagManager)
+
+        // set the flag
+        setByBroadcast(1, "new")
+        verifyPutData(1, "{\"type\":\"string\",\"value\":\"new\"}", numReads = 2)
+        whenever(mFlagManager.readFlagValue<String>(eq(1), any())).thenReturn("new")
+
+        assertThat(mFeatureFlagsDebug.getString(flag1)).isEqualTo("new")
+        verify(mFlagManager, times(3)).readFlagValue(eq(1), eq(StringFlagSerializer))
+    }
+
+    private fun verifyPutData(id: Int, data: String, numReads: Int = 1) {
+        inOrder(mFlagManager, mSecureSettings).apply {
+            verify(mFlagManager, times(numReads)).readFlagValue(eq(id), any<FlagSerializer<*>>())
+            verify(mFlagManager).idToSettingsKey(eq(id))
+            verify(mSecureSettings).putString(eq("key-$id"), eq(data))
+            verify(mFlagManager).dispatchListenersAndMaybeRestart(eq(id))
+        }.verifyNoMoreInteractions()
+        verifyNoMoreInteractions(mFlagManager, mSecureSettings)
+    }
+
+    private fun setByBroadcast(id: Int, value: Serializable?) {
+        val intent = Intent(FlagManager.ACTION_SET_FLAG)
+        intent.putExtra(FlagManager.EXTRA_ID, id)
+        intent.putExtra(FlagManager.EXTRA_VALUE, value)
+        mBroadcastReceiver.onReceive(mMockContext, intent)
+    }
+
+    private fun <F : Flag<*>> addFlag(flag: F): F {
+        val old = mFlagMap.put(flag.id, flag)
+        check(old == null) { "Flag ${flag.id} already registered" }
+        return flag
+    }
+
+    @Test
+    fun testDump() {
+        val flag1 = BooleanFlag(1, true)
+        val flag2 = ResourceBooleanFlag(2, 1002)
+        val flag3 = BooleanFlag(3, false)
+        val flag4 = StringFlag(4, "")
+        val flag5 = StringFlag(5, "flag5default")
+        val flag6 = ResourceStringFlag(6, 1006)
+        val flag7 = ResourceStringFlag(7, 1007)
+
+        whenever(mResources.getBoolean(1002)).thenReturn(true)
+        whenever(mResources.getString(1006)).thenReturn("resource1006")
+        whenever(mResources.getString(1007)).thenReturn("resource1007")
+        whenever(mFlagManager.readFlagValue(eq(7), eq(StringFlagSerializer)))
+            .thenReturn("override7")
+
+        // WHEN the flags have been accessed
+        assertThat(mFeatureFlagsDebug.isEnabled(flag1)).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(flag2)).isTrue()
+        assertThat(mFeatureFlagsDebug.isEnabled(flag3)).isFalse()
+        assertThat(mFeatureFlagsDebug.getString(flag4)).isEmpty()
+        assertThat(mFeatureFlagsDebug.getString(flag5)).isEqualTo("flag5default")
+        assertThat(mFeatureFlagsDebug.getString(flag6)).isEqualTo("resource1006")
+        assertThat(mFeatureFlagsDebug.getString(flag7)).isEqualTo("override7")
+
+        // THEN the dump contains the flags and the default values
+        val dump = dumpToString()
+        assertThat(dump).contains(" sysui_flag_1: true\n")
+        assertThat(dump).contains(" sysui_flag_2: true\n")
+        assertThat(dump).contains(" sysui_flag_3: false\n")
+        assertThat(dump).contains(" sysui_flag_4: [length=0] \"\"\n")
+        assertThat(dump).contains(" sysui_flag_5: [length=12] \"flag5default\"\n")
+        assertThat(dump).contains(" sysui_flag_6: [length=12] \"resource1006\"\n")
+        assertThat(dump).contains(" sysui_flag_7: [length=9] \"override7\"\n")
+    }
+
+    private fun dumpToString(): String {
+        val sw = StringWriter()
+        val pw = PrintWriter(sw)
+        mFeatureFlagsDebug.dump(mock(), pw, emptyArray<String>())
+        pw.flush()
+        return sw.toString()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
deleted file mode 100644
index 475dde2..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.java
+++ /dev/null
@@ -1,90 +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.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.content.Context;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dump.DumpManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
- * overriding, and should never return any value other than the one provided as the default.
- */
-@SmallTest
-public class FeatureFlagsReleaseTest extends SysuiTestCase {
-    FeatureFlagsRelease mFeatureFlagsRelease;
-
-    @Mock private DumpManager mDumpManager;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-
-        mFeatureFlagsRelease = new FeatureFlagsRelease(mDumpManager);
-    }
-
-    @After
-    public void onFinished() {
-        // The dump manager should be registered with even for the release version, but that's it.
-        verify(mDumpManager).registerDumpable(anyString(), any());
-        verifyNoMoreInteractions(mDumpManager);
-    }
-
-    @Test
-    public void testDump() {
-        // WHEN the flags have been accessed
-        assertFalse(mFeatureFlagsRelease.isEnabled(1, false));
-        assertTrue(mFeatureFlagsRelease.isEnabled(2, true));
-
-        // THEN the dump contains the flags and the default values
-        String dump = dumpToString();
-        assertThat(dump).contains(" sysui_flag_1: false\n");
-        assertThat(dump).contains(" sysui_flag_2: true\n");
-    }
-
-    private String dumpToString() {
-        StringWriter sw = new StringWriter();
-        PrintWriter pw = new PrintWriter(sw);
-        mFeatureFlagsRelease.dump(mock(FileDescriptor.class), pw, new String[0]);
-        pw.flush();
-        String dump = sw.toString();
-        return dump;
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
new file mode 100644
index 0000000..b5e6602
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+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.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+class FeatureFlagsReleaseTest : SysuiTestCase() {
+    private lateinit var mFeatureFlagsRelease: FeatureFlagsRelease
+
+    @Mock private lateinit var mResources: Resources
+    @Mock private lateinit var mDumpManager: DumpManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFeatureFlagsRelease = FeatureFlagsRelease(mResources, mDumpManager)
+    }
+
+    @After
+    fun onFinished() {
+        // The dump manager should be registered with even for the release version, but that's it.
+        verify(mDumpManager).registerDumpable(any(), any())
+        verifyNoMoreInteractions(mDumpManager)
+    }
+
+    @Test
+    fun testBooleanResourceFlag() {
+        val flagId = 213
+        val flagResourceId = 3
+        val flag = ResourceBooleanFlag(flagId, flagResourceId)
+        whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
+        assertThat(mFeatureFlagsRelease.isEnabled(flag)).isTrue()
+    }
+
+    @Test
+    fun testReadResourceStringFlag() {
+        whenever(mResources.getString(1001)).thenReturn("")
+        whenever(mResources.getString(1002)).thenReturn("res2")
+        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")
+
+        assertThrows(NullPointerException::class.java) {
+            mFeatureFlagsRelease.getString(ResourceStringFlag(3, 1003))
+        }
+        assertThrows(NameNotFoundException::class.java) {
+            mFeatureFlagsRelease.getString(ResourceStringFlag(4, 1004))
+        }
+    }
+
+    @Test
+    fun testDump() {
+        val flag1 = BooleanFlag(1, true)
+        val flag2 = ResourceBooleanFlag(2, 1002)
+        val flag3 = BooleanFlag(3, false)
+        val flag4 = StringFlag(4, "")
+        val flag5 = StringFlag(5, "flag5default")
+        val flag6 = ResourceStringFlag(6, 1006)
+
+        whenever(mResources.getBoolean(1002)).thenReturn(true)
+        whenever(mResources.getString(1006)).thenReturn("resource1006")
+        whenever(mResources.getString(1007)).thenReturn("resource1007")
+
+        // WHEN the flags have been accessed
+        assertThat(mFeatureFlagsRelease.isEnabled(flag1)).isTrue()
+        assertThat(mFeatureFlagsRelease.isEnabled(flag2)).isTrue()
+        assertThat(mFeatureFlagsRelease.isEnabled(flag3)).isFalse()
+        assertThat(mFeatureFlagsRelease.getString(flag4)).isEmpty()
+        assertThat(mFeatureFlagsRelease.getString(flag5)).isEqualTo("flag5default")
+        assertThat(mFeatureFlagsRelease.getString(flag6)).isEqualTo("resource1006")
+
+        // THEN the dump contains the flags and the default values
+        val dump = dumpToString()
+        assertThat(dump).contains(" sysui_flag_1: true\n")
+        assertThat(dump).contains(" sysui_flag_2: true\n")
+        assertThat(dump).contains(" sysui_flag_3: false\n")
+        assertThat(dump).contains(" sysui_flag_4: [length=0] \"\"\n")
+        assertThat(dump).contains(" sysui_flag_5: [length=12] \"flag5default\"\n")
+        assertThat(dump).contains(" sysui_flag_6: [length=12] \"resource1006\"\n")
+    }
+
+    private fun dumpToString(): String {
+        val sw = StringWriter()
+        val pw = PrintWriter(sw)
+        mFeatureFlagsRelease.dump(mock(), pw, emptyArray())
+        pw.flush()
+        return sw.toString()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
new file mode 100644
index 0000000..644bd21
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -0,0 +1,302 @@
+/*
+ * 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.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.function.Consumer
+import org.mockito.Mockito.`when` as whenever
+
+/**
+ * NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
+ * overriding, and should never return any value other than the one provided as the default.
+ */
+@SmallTest
+class FlagManagerTest : SysuiTestCase() {
+    private lateinit var mFlagManager: FlagManager
+
+    @Mock private lateinit var mMockContext: Context
+    @Mock private lateinit var mFlagSettingsHelper: FlagSettingsHelper
+    @Mock private lateinit var mHandler: Handler
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mFlagManager = FlagManager(mMockContext, mFlagSettingsHelper, mHandler)
+    }
+
+    @Test
+    fun testContentObserverAddedAndRemoved() {
+        val listener1 = mock<FlagListenable.Listener>()
+        val listener2 = mock<FlagListenable.Listener>()
+
+        // no interactions before adding listener
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // adding the first listener registers the observer
+        mFlagManager.addListener(BooleanFlag(1, true), listener1)
+        val observer = withArgCaptor<ContentObserver> {
+            verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
+        }
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // adding another listener does nothing
+        mFlagManager.addListener(BooleanFlag(2, true), listener2)
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // removing the original listener does nothing with second one still present
+        mFlagManager.removeListener(listener1)
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+
+        // removing the final listener unregisters the observer
+        mFlagManager.removeListener(listener2)
+        verify(mFlagSettingsHelper).unregisterContentObserver(eq(observer))
+        verifyNoMoreInteractions(mFlagSettingsHelper)
+    }
+
+    @Test
+    fun testObserverClearsCache() {
+        val listener = mock<FlagListenable.Listener>()
+        val clearCacheAction = mock<Consumer<Int>>()
+        mFlagManager.clearCacheAction = clearCacheAction
+        mFlagManager.addListener(BooleanFlag(1, true), listener)
+        val observer = withArgCaptor<ContentObserver> {
+            verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
+        }
+        observer.onChange(false, flagUri(1))
+        verify(clearCacheAction).accept(eq(1))
+    }
+
+    @Test
+    fun testObserverInvokesListeners() {
+        val listener1 = mock<FlagListenable.Listener>()
+        val listener10 = mock<FlagListenable.Listener>()
+        mFlagManager.addListener(BooleanFlag(1, true), listener1)
+        mFlagManager.addListener(BooleanFlag(10, true), listener10)
+        val observer = withArgCaptor<ContentObserver> {
+            verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
+        }
+        observer.onChange(false, flagUri(1))
+        val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener1).onFlagChanged(capture())
+        }
+        assertThat(flagEvent1.flagId).isEqualTo(1)
+        verifyNoMoreInteractions(listener1, listener10)
+
+        observer.onChange(false, flagUri(10))
+        val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener10).onFlagChanged(capture())
+        }
+        assertThat(flagEvent10.flagId).isEqualTo(10)
+        verifyNoMoreInteractions(listener1, listener10)
+    }
+
+    fun flagUri(id: Int): Uri = Uri.parse("content://settings/system/systemui/flags/$id")
+
+    @Test
+    fun testOnlySpecificFlagListenerIsInvoked() {
+        val listener1 = mock<FlagListenable.Listener>()
+        val listener10 = mock<FlagListenable.Listener>()
+        mFlagManager.addListener(BooleanFlag(1, true), listener1)
+        mFlagManager.addListener(BooleanFlag(10, true), listener10)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener1).onFlagChanged(capture())
+        }
+        assertThat(flagEvent1.flagId).isEqualTo(1)
+        verifyNoMoreInteractions(listener1, listener10)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(10)
+        val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener10).onFlagChanged(capture())
+        }
+        assertThat(flagEvent10.flagId).isEqualTo(10)
+        verifyNoMoreInteractions(listener1, listener10)
+    }
+
+    @Test
+    fun testSameListenerCanBeUsedForMultipleFlags() {
+        val listener = mock<FlagListenable.Listener>()
+        mFlagManager.addListener(BooleanFlag(1, true), listener)
+        mFlagManager.addListener(BooleanFlag(10, true), listener)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener).onFlagChanged(capture())
+        }
+        assertThat(flagEvent1.flagId).isEqualTo(1)
+        verifyNoMoreInteractions(listener)
+
+        mFlagManager.dispatchListenersAndMaybeRestart(10)
+        val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
+            verify(listener, times(2)).onFlagChanged(capture())
+        }
+        assertThat(flagEvent10.flagId).isEqualTo(10)
+        verifyNoMoreInteractions(listener)
+    }
+
+    @Test
+    fun testRestartWithNoListeners() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(false))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testListenerCanSuppressRestart() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.addListener(BooleanFlag(1, true)) { event ->
+            event.requestNoRestart()
+        }
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(true))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testListenerOnlySuppressesRestartForOwnFlag() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.addListener(BooleanFlag(10, true)) { event ->
+            event.requestNoRestart()
+        }
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(false))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testRestartWhenNotAllListenersRequestSuppress() {
+        val restartAction = mock<Consumer<Boolean>>()
+        mFlagManager.restartAction = restartAction
+        mFlagManager.addListener(BooleanFlag(10, true)) { event ->
+            event.requestNoRestart()
+        }
+        mFlagManager.addListener(BooleanFlag(10, true)) {
+            // do not request
+        }
+        mFlagManager.dispatchListenersAndMaybeRestart(1)
+        verify(restartAction).accept(eq(false))
+        verifyNoMoreInteractions(restartAction)
+    }
+
+    @Test
+    fun testReadBooleanFlag() {
+        // test that null string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+
+        // test that empty string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+
+        // test false
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"boolean\",\"value\":false}")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse()
+
+        // test true
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"boolean\",\"value\":true}")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue()
+
+        // Reading a value of a different type should just return null
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
+        assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
+
+        // Reading a value that isn't json should throw an exception
+        assertThrows(InvalidFlagStorageException::class.java) {
+            whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
+            mFlagManager.readFlagValue(1, BooleanFlagSerializer)
+        }
+    }
+
+    @Test
+    fun testSerializeBooleanFlag() {
+        // test false
+        assertThat(BooleanFlagSerializer.toSettingsData(false))
+            .isEqualTo("{\"type\":\"boolean\",\"value\":false}")
+
+        // test true
+        assertThat(BooleanFlagSerializer.toSettingsData(true))
+            .isEqualTo("{\"type\":\"boolean\",\"value\":true}")
+    }
+
+    @Test
+    fun testReadStringFlag() {
+        // test that null string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+
+        // test that empty string returns null
+        whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+
+        // test json with the empty string value returns empty string
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"string\",\"value\":\"\"}")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("")
+
+        // test string with value is returned
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo")
+
+        // Reading a value of a different type should just return null
+        whenever(mFlagSettingsHelper.getString(any()))
+            .thenReturn("{\"type\":\"boolean\",\"value\":false}")
+        assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
+
+        // Reading a value that isn't json should throw an exception
+        assertThrows(InvalidFlagStorageException::class.java) {
+            whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
+            mFlagManager.readFlagValue(1, StringFlagSerializer)
+        }
+    }
+
+    @Test
+    fun testSerializeStringFlag() {
+        // test empty string
+        assertThat(StringFlagSerializer.toSettingsData(""))
+            .isEqualTo("{\"type\":\"string\",\"value\":\"\"}")
+
+        // test string "foo"
+        assertThat(StringFlagSerializer.toSettingsData("foo"))
+            .isEqualTo("{\"type\":\"string\",\"value\":\"foo\"}")
+    }
+}
\ No newline at end of file
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 bf5522c..e3a7e3b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -62,6 +62,7 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.telephony.TelephonyListenerManager;
@@ -117,6 +118,7 @@
     @Mock private StatusBar mStatusBar;
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogLaunchAnimator mDialogLaunchAnimator;
+    @Mock private SystemUIDialogManager mDialogManager;
 
     private TestableLooper mTestableLooper;
 
@@ -162,7 +164,8 @@
                 mPackageManager,
                 Optional.of(mStatusBar),
                 mKeyguardUpdateMonitor,
-                mDialogLaunchAnimator);
+                mDialogLaunchAnimator,
+                mDialogManager);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
         ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
new file mode 100644
index 0000000..f3043e9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -0,0 +1,134 @@
+package com.android.systemui.keyguard
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.graphics.Point
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.ViewRootImpl
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.shared.system.smartspace.SmartspaceTransitionController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor.forClass
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
+    private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+
+    @Mock
+    private lateinit var keyguardViewMediator: KeyguardViewMediator
+    @Mock
+    private lateinit var keyguardStateController: KeyguardStateController
+    @Mock
+    private lateinit var keyguardViewController: KeyguardViewController
+    @Mock
+    private lateinit var smartspaceTransitionController: SmartspaceTransitionController
+    @Mock
+    private lateinit var featureFlags: FeatureFlags
+    @Mock
+    private lateinit var biometricUnlockController: BiometricUnlockController
+    @Mock
+    private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+
+    private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
+            context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
+            smartspaceTransitionController, featureFlags, biometricUnlockController
+        )
+
+        `when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
+
+        // All of these fields are final, so we can't mock them, but are needed so that the surface
+        // appear amount setter doesn't short circuit.
+        remoteAnimationTarget = RemoteAnimationTarget(
+            0, 0, null, false, Rect(), Rect(), 0, Point(), Rect(), Rect(),
+            mock(WindowConfiguration::class.java), false, mock(SurfaceControl::class.java), Rect(),
+            mock(ActivityManager.RunningTaskInfo::class.java), false)
+
+        // Set the surface applier to our mock so that we can verify the arguments passed to it.
+        // This applier does not have any side effects within the unlock animation controller, so
+        // this is a reasonable way to test.
+        keyguardUnlockAnimationController.surfaceTransactionApplier = surfaceTransactionApplier
+    }
+
+    /**
+     * If we're wake and unlocking, we are animating from the black/AOD screen to the app/launcher
+     * underneath. The LightRevealScrim will animate circularly from the fingerprint reader,
+     * revealing the app/launcher below. In this case, we want to make sure we are not animating the
+     * surface, or the user will see the wallpaper briefly as the app animates in.
+     */
+    @Test
+    fun noSurfaceAnimation_ifWakeAndUnlocking() {
+        `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+
+        keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation(
+            remoteAnimationTarget,
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
+        )
+
+        val captor = forClass(SyncRtSurfaceTransactionApplier.SurfaceParams::class.java)
+        verify(surfaceTransactionApplier, times(1)).scheduleApply(captor.capture())
+
+        val params = captor.value
+
+        // We expect that we've instantly set the surface behind to alpha = 1f, and have no
+        // transforms (translate, scale) on its matrix.
+        assertEquals(params.alpha, 1f)
+        assertTrue(params.matrix.isIdentity)
+
+        // Also expect we've immediately asked the keyguard view mediator to finish the remote
+        // animation.
+        verify(keyguardViewMediator, times(1)).onKeyguardExitRemoteAnimationFinished(
+            false /* cancelled */)
+
+        verifyNoMoreInteractions(surfaceTransactionApplier)
+    }
+
+    /**
+     * If we are not wake and unlocking, we expect the unlock animation to play normally.
+     */
+    @Test
+    fun surfaceAnimation_ifNotWakeAndUnlocking() {
+        `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(false)
+
+        keyguardUnlockAnimationController.notifyStartKeyguardExitAnimation(
+            remoteAnimationTarget,
+            0 /* startTime */,
+            false /* requestedShowSurfaceBehindKeyguard */
+        )
+
+        // Make sure the animator was started.
+        assertTrue(keyguardUnlockAnimationController.surfaceBehindEntryAnimator.isRunning)
+
+        // Since the animation is running, we should not have finished the remote animation.
+        verify(keyguardViewMediator, times(0)).onKeyguardExitRemoteAnimationFinished(
+            false /* cancelled */)
+    }
+}
\ No newline at end of file
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 b774daf..27fcb11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -54,10 +54,11 @@
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 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.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
 import com.android.systemui.util.DeviceConfigProxy;
@@ -69,7 +70,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatchers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -95,62 +95,40 @@
     private @Mock NavigationModeController mNavigationModeController;
     private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
     private @Mock DozeParameters mDozeParameters;
-    private @Mock Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent;
-    private @Mock Optional<UnfoldLightRevealOverlayAnimation> mUnfoldAnimationOptional;
+    private @Mock SysUIUnfoldComponent mSysUIUnfoldComponent;
     private @Mock UnfoldLightRevealOverlayAnimation mUnfoldAnimation;
     private @Mock SysuiStatusBarStateController mStatusBarStateController;
     private @Mock KeyguardStateController mKeyguardStateController;
     private @Mock NotificationShadeDepthController mNotificationShadeDepthController;
     private @Mock KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    private @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private @Mock ScreenOffAnimationController mScreenOffAnimationController;
+    private @Mock FoldAodAnimationController mFoldAodAnimationController;
     private @Mock IKeyguardDrawnCallback mKeyguardDrawnCallback;
     private @Mock InteractionJankMonitor mInteractionJankMonitor;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
+    private Optional<SysUIUnfoldComponent> mSysUiUnfoldComponentOptional;
+
     private FalsingCollectorFake mFalsingCollector;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mFalsingCollector = new FalsingCollectorFake();
+        mSysUiUnfoldComponentOptional = Optional.of(mSysUIUnfoldComponent);
 
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
-        when(mSysUIUnfoldComponent.map(
-                ArgumentMatchers.<Function<SysUIUnfoldComponent, UnfoldLightRevealOverlayAnimation>>
-                        any()))
-            .thenReturn(mUnfoldAnimationOptional);
-        when(mUnfoldAnimationOptional.isPresent()).thenReturn(true);
-        when(mUnfoldAnimationOptional.get()).thenReturn(mUnfoldAnimation);
+        when(mSysUIUnfoldComponent.getUnfoldLightRevealOverlayAnimation())
+                .thenReturn(mUnfoldAnimation);
+        when(mSysUIUnfoldComponent.getFoldAodAnimationController())
+                .thenReturn(mFoldAodAnimationController);
+
         when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
         when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
 
-        mViewMediator = new KeyguardViewMediator(
-                mContext,
-                mFalsingCollector,
-                mLockPatternUtils,
-                mBroadcastDispatcher,
-                () -> mStatusBarKeyguardViewManager,
-                mDismissCallbackRegistry,
-                mUpdateMonitor,
-                mDumpManager,
-                mUiBgExecutor,
-                mPowerManager,
-                mTrustManager,
-                mUserSwitcherController,
-                mDeviceConfig,
-                mNavigationModeController,
-                mKeyguardDisplayManager,
-                mDozeParameters,
-                mSysUIUnfoldComponent,
-                mStatusBarStateController,
-                mKeyguardStateController,
-                () -> mKeyguardUnlockAnimationController,
-                mUnlockedScreenOffAnimationController,
-                () -> mNotificationShadeDepthController,
-                mInteractionJankMonitor);
-        mViewMediator.start();
+        createAndStartViewMediator();
     }
 
     @Test
@@ -179,6 +157,7 @@
         mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
         TestableLooper.get(this).processAllMessages();
         onUnfoldOverlayReady();
+        onFoldAodReady();
 
         // Should be called when both unfold overlay and keyguard drawn ready
         verify(mKeyguardDrawnCallback).onDrawn();
@@ -188,7 +167,8 @@
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback()
             throws RemoteException {
-        when(mUnfoldAnimationOptional.isPresent()).thenReturn(false);
+        mSysUiUnfoldComponentOptional = Optional.empty();
+        createAndStartViewMediator();
 
         mViewMediator.onScreenTurningOn(mKeyguardDrawnCallback);
         TestableLooper.get(this).processAllMessages();
@@ -200,6 +180,7 @@
     @Test
     public void testIsAnimatingScreenOff() {
         when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+        when(mDozeParameters.shouldAnimateDozingChange()).thenReturn(true);
 
         mViewMediator.onFinishedGoingToSleep(OFF_BECAUSE_OF_USER, false);
         mViewMediator.setDozing(true);
@@ -244,4 +225,39 @@
         overlayReadyCaptor.getValue().run();
         TestableLooper.get(this).processAllMessages();
     }
+
+    private void onFoldAodReady() {
+        ArgumentCaptor<Runnable> ready = ArgumentCaptor.forClass(Runnable.class);
+        verify(mFoldAodAnimationController).onScreenTurningOn(ready.capture());
+        ready.getValue().run();
+        TestableLooper.get(this).processAllMessages();
+    }
+
+    private void createAndStartViewMediator() {
+        mViewMediator = new KeyguardViewMediator(
+                mContext,
+                mFalsingCollector,
+                mLockPatternUtils,
+                mBroadcastDispatcher,
+                () -> mStatusBarKeyguardViewManager,
+                mDismissCallbackRegistry,
+                mUpdateMonitor,
+                mDumpManager,
+                mUiBgExecutor,
+                mPowerManager,
+                mTrustManager,
+                mUserSwitcherController,
+                mDeviceConfig,
+                mNavigationModeController,
+                mKeyguardDisplayManager,
+                mDozeParameters,
+                mSysUiUnfoldComponentOptional,
+                mStatusBarStateController,
+                mKeyguardStateController,
+                () -> mKeyguardUnlockAnimationController,
+                mScreenOffAnimationController,
+                () -> mNotificationShadeDepthController,
+                mInteractionJankMonitor);
+        mViewMediator.start();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
index e01583e..d7c00fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -16,15 +16,16 @@
 
 package com.android.systemui.keyguard;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
 
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -41,7 +42,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -57,6 +57,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.doze.util.BurnInHelperKt;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -66,8 +67,7 @@
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
-import com.airbnb.lottie.LottieAnimationView;
-
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -76,6 +76,8 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -86,6 +88,8 @@
 public class LockIconViewControllerTest extends SysuiTestCase {
     private static final String UNLOCKED_LABEL = "unlocked";
 
+    private MockitoSession mStaticMockSession;
+
     private @Mock LockIconView mLockIconView;
     private @Mock AnimatedStateListDrawable mIconDrawable;
     private @Mock Context mContext;
@@ -102,8 +106,6 @@
     private @Mock ConfigurationController mConfigurationController;
     private @Mock Vibrator mVibrator;
     private @Mock AuthRippleController mAuthRippleController;
-    private @Mock LottieAnimationView mAodFp;
-    private @Mock LayoutInflater mLayoutInflater;
     private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
 
     private LockIconViewController mLockIconViewController;
@@ -133,11 +135,14 @@
 
     @Before
     public void setUp() throws Exception {
+        mStaticMockSession = mockitoSession()
+                .mockStatic(BurnInHelperKt.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
         MockitoAnnotations.initMocks(this);
 
         when(mLockIconView.getResources()).thenReturn(mResources);
         when(mLockIconView.getContext()).thenReturn(mContext);
-        when(mLockIconView.findViewById(R.layout.udfps_aod_lock_icon)).thenReturn(mAodFp);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
         Rect windowBounds = new Rect(0, 0, 800, 1200);
@@ -164,38 +169,13 @@
                 mDelayableExecutor,
                 mVibrator,
                 mAuthRippleController,
-                mResources,
-                mLayoutInflater
+                mResources
         );
     }
 
-    @Test
-    public void testIgnoreUdfpsWhenNotSupported() {
-        // GIVEN Udpfs sensor is NOT available
-        mLockIconViewController.init();
-        captureAttachListener();
-
-        // WHEN the view is attached
-        mAttachListener.onViewAttachedToWindow(mLockIconView);
-
-        // THEN lottie animation should NOT be inflated
-        verify(mLayoutInflater, never()).inflate(eq(R.layout.udfps_aod_lock_icon), any());
-    }
-
-    @Test
-    public void testInflateUdfpsWhenSupported() {
-        // GIVEN Udpfs sensor is available
-        setupUdfps();
-        when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
-        mLockIconViewController.init();
-        captureAttachListener();
-
-        // WHEN the view is attached
-        mAttachListener.onViewAttachedToWindow(mLockIconView);
-
-        // THEN lottie animation should be inflated
-        verify(mLayoutInflater).inflate(eq(R.layout.udfps_aod_lock_icon), any());
+    @After
+    public void tearDown() {
+        mStaticMockSession.finishMocking();
     }
 
     @Test
@@ -369,6 +349,42 @@
         verify(mLockIconView).updateIcon(ICON_LOCK, true);
     }
 
+    @Test
+    public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
+        // GIVEN udfps enrolled
+        setupUdfps();
+        when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+        // GIVEN burn-in offset = 5
+        int burnInOffset = 5;
+        when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
+
+        // GIVEN starting state for the lock icon (keyguard)
+        setupShowLockIcon();
+        mLockIconViewController.init();
+        captureAttachListener();
+        mAttachListener.onViewAttachedToWindow(mLockIconView);
+        captureStatusBarStateListener();
+        reset(mLockIconView);
+
+        // WHEN dozing updates
+        mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+        mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+
+        // THEN the view's translation is updated to use the AoD burn-in offsets
+        verify(mLockIconView).setTranslationY(burnInOffset);
+        verify(mLockIconView).setTranslationX(burnInOffset);
+        reset(mLockIconView);
+
+        // WHEN the device is no longer dozing
+        mStatusBarStateListener.onDozingChanged(false /* isDozing */);
+        mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
+
+        // THEN the view is updated to NO translation (no burn-in offsets anymore)
+        verify(mLockIconView).setTranslationY(0);
+        verify(mLockIconView).setTranslationX(0);
+
+    }
     private Pair<Integer, PointF> setupUdfps() {
         when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
         final PointF udfpsLocation = new PointF(50, 75);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 41ce941..7cc0172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -19,6 +19,7 @@
 import android.content.Intent
 import android.graphics.Color
 import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.Icon
 import android.graphics.drawable.RippleDrawable
 import android.media.MediaMetadata
 import android.media.session.MediaSession
@@ -97,6 +98,7 @@
     @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
     @Mock private lateinit var mediaCarouselController: MediaCarouselController
     @Mock private lateinit var falsingManager: FalsingManager
+    @Mock private lateinit var mediaFlags: MediaFlags
     private lateinit var appIcon: ImageView
     private lateinit var albumView: ImageView
     private lateinit var titleText: TextView
@@ -123,6 +125,7 @@
     private lateinit var session: MediaSession
     private val device = MediaDeviceData(true, null, DEVICE_NAME)
     private val disabledDevice = MediaDeviceData(false, null, "Disabled Device")
+    private lateinit var mediaData: MediaData
     private val clock = FakeSystemClock()
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -135,7 +138,7 @@
 
         player = MediaControlPanel(context, bgExecutor, activityStarter, mediaViewController,
             seekBarViewModel, Lazy { mediaDataManager }, keyguardDismissUtil,
-            mediaOutputDialogFactory, mediaCarouselController, falsingManager, clock)
+            mediaOutputDialogFactory, mediaCarouselController, falsingManager, mediaFlags, clock)
         whenever(seekBarViewModel.progress).thenReturn(seekBarData)
 
         // Mock out a view holder for the player to attach to.
@@ -165,14 +168,19 @@
         whenever(holder.totalTimeView).thenReturn(totalTimeView)
         action0 = ImageButton(context)
         whenever(holder.action0).thenReturn(action0)
+        whenever(holder.getAction(R.id.action0)).thenReturn(action0)
         action1 = ImageButton(context)
         whenever(holder.action1).thenReturn(action1)
+        whenever(holder.getAction(R.id.action1)).thenReturn(action1)
         action2 = ImageButton(context)
         whenever(holder.action2).thenReturn(action2)
+        whenever(holder.getAction(R.id.action2)).thenReturn(action2)
         action3 = ImageButton(context)
         whenever(holder.action3).thenReturn(action3)
+        whenever(holder.getAction(R.id.action3)).thenReturn(action3)
         action4 = ImageButton(context)
         whenever(holder.action4).thenReturn(action4)
+        whenever(holder.getAction(R.id.action4)).thenReturn(action4)
         whenever(holder.longPressText).thenReturn(longPressText)
         whenever(longPressText.handler).thenReturn(handler)
         settings = View(context)
@@ -200,6 +208,26 @@
             setPlaybackState(playbackBuilder.build())
         }
         session.setActive(true)
+
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = device,
+                active = true,
+                resumeAction = null)
+
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
     }
 
     @After
@@ -210,18 +238,50 @@
 
     @Test
     fun bindWhenUnattached() {
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, null, null, device, true, null)
+        val state = mediaData.copy(token = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(player.isPlaying()).isFalse()
     }
 
     @Test
+    fun bindSemanticActions() {
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val icon = Icon.createWithResource(context, android.R.drawable.ic_media_play)
+        val semanticActions = MediaButton(
+                playOrPause = MediaAction(icon, Runnable {}, "play"),
+                nextOrCustom = MediaAction(icon, Runnable {}, "next"),
+                startCustom = MediaAction(icon, null, "custom 1"),
+                endCustom = MediaAction(icon, null, "custom 2")
+        )
+        val state = mediaData.copy(semanticActions = semanticActions)
+
+        player.attachPlayer(holder)
+        player.bindPlayer(state, PACKAGE)
+
+        verify(expandedSet).setVisibility(R.id.action0, ConstraintSet.VISIBLE)
+        assertThat(action0.contentDescription).isEqualTo("custom 1")
+        assertThat(action0.isEnabled()).isFalse()
+
+        verify(expandedSet).setVisibility(R.id.action1, ConstraintSet.INVISIBLE)
+        assertThat(action1.isEnabled()).isFalse()
+
+        verify(expandedSet).setVisibility(R.id.action2, ConstraintSet.VISIBLE)
+        assertThat(action2.isEnabled()).isTrue()
+        assertThat(action2.contentDescription).isEqualTo("play")
+
+        verify(expandedSet).setVisibility(R.id.action3, ConstraintSet.VISIBLE)
+        assertThat(action3.isEnabled()).isTrue()
+        assertThat(action3.contentDescription).isEqualTo("next")
+
+        verify(expandedSet).setVisibility(R.id.action4, ConstraintSet.VISIBLE)
+        assertThat(action4.contentDescription).isEqualTo("custom 2")
+        assertThat(action4.isEnabled()).isFalse()
+    }
+
+    @Test
     fun bindText() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bindPlayer(state, PACKAGE)
+        player.bindPlayer(mediaData, PACKAGE)
         assertThat(titleText.getText()).isEqualTo(TITLE)
         assertThat(artistText.getText()).isEqualTo(ARTIST)
     }
@@ -229,9 +289,7 @@
     @Test
     fun bindDevice() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
-        player.bindPlayer(state, PACKAGE)
+        player.bindPlayer(mediaData, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.contentDescription).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isTrue()
@@ -242,8 +300,7 @@
         seamless.id = 1
         val fallbackString = context.getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
+        val state = mediaData.copy(device = disabledDevice)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isFalse()
         assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -254,8 +311,7 @@
     fun bindNullDevice() {
         val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
+        val state = mediaData.copy(device = null)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamless.isEnabled()).isTrue()
         assertThat(seamlessText.getText()).isEqualTo(fallbackString)
@@ -265,9 +321,7 @@
     @Test
     fun bindDeviceResumptionPlayer() {
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
-                resumption = true)
+        val state = mediaData.copy(resumption = true)
         player.bindPlayer(state, PACKAGE)
         assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
         assertThat(seamless.isEnabled()).isFalse()
@@ -323,9 +377,7 @@
     fun dismissButtonClick() {
         val mediaKey = "key for dismissal"
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                notificationKey = KEY)
+        val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(true)
@@ -337,9 +389,7 @@
     fun dismissButtonDisabled() {
         val mediaKey = "key for dismissal"
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                isClearable = false, notificationKey = KEY)
+        val state = mediaData.copy(isClearable = false, notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(false)
@@ -351,9 +401,7 @@
         whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false)
 
         player.attachPlayer(holder)
-        val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-                emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null,
-                notificationKey = KEY)
+        val state = mediaData.copy(notificationKey = KEY)
         player.bindPlayer(state, mediaKey)
 
         assertThat(dismiss.isEnabled).isEqualTo(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 09c83e5..7a487b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -74,8 +74,9 @@
         mManager = new MediaDataCombineLatest();
         mManager.addListener(mListener);
 
-        mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-                new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null,
+        mMediaData = new MediaData(
+                USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+                new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
                 MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index 5a3c43c..6b203bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -96,11 +96,24 @@
         setUser(USER_MAIN)
 
         // Set up test media data
-        dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
-            emptyList(), PACKAGE, null, null, device, true, null)
-
-        dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
-            emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+        dataMain = MediaData(
+                userId = USER_MAIN,
+                initialized = true,
+                backgroundColor = BG_COLOR,
+                app = APP,
+                appIcon = null,
+                artist = ARTIST,
+                song = TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = null,
+                clickIntent = null,
+                device = device,
+                active = true,
+                resumeAction = null)
+        dataGuest = dataMain.copy(userId = USER_GUEST)
 
         `when`(smartspaceData.targetId).thenReturn(SMARTSPACE_KEY)
         `when`(smartspaceData.isActive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index e2019e0..649ee87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -11,12 +11,15 @@
 import android.media.MediaMetadata
 import android.media.session.MediaController
 import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.os.Bundle
 import android.provider.Settings
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dump.DumpManager
@@ -67,6 +70,7 @@
     @JvmField @Rule val mockito = MockitoJUnit.rule()
     @Mock lateinit var mediaControllerFactory: MediaControllerFactory
     @Mock lateinit var controller: MediaController
+    @Mock lateinit var transportControls: MediaController.TransportControls
     @Mock lateinit var playbackInfo: MediaController.PlaybackInfo
     lateinit var session: MediaSession
     lateinit var metadataBuilder: MediaMetadata.Builder
@@ -87,6 +91,7 @@
     @Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
     @Mock private lateinit var mediaRecommendationItem: SmartspaceAction
     @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
+    @Mock private lateinit var mediaFlags: MediaFlags
     lateinit var mediaDataManager: MediaDataManager
     lateinit var mediaNotification: StatusBarNotification
     @Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -122,7 +127,8 @@
             useMediaResumption = true,
             useQsMediaPlayer = true,
             systemClock = clock,
-            tunerService = tunerService
+            tunerService = tunerService,
+            mediaFlags = mediaFlags
         )
         verify(tunerService).addTunable(capture(tunableCaptor),
                 eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -140,6 +146,7 @@
             putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
         }
         whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
+        whenever(controller.transportControls).thenReturn(transportControls)
         whenever(controller.playbackInfo).thenReturn(playbackInfo)
         whenever(playbackInfo.playbackType).thenReturn(
                 MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL)
@@ -161,6 +168,7 @@
         whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
         whenever(mediaSmartspaceTarget.iconGrid).thenReturn(listOf(mediaRecommendationItem))
         whenever(mediaSmartspaceTarget.creationTimeMillis).thenReturn(1234L)
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(false)
     }
 
     @After
@@ -583,4 +591,157 @@
         assertThat(mediaDataCaptor.value.actionsToShowInCompact.size).isEqualTo(
                 MediaDataManager.MAX_COMPACT_ACTIONS)
     }
+
+    @Test
+    fun testPlaybackActions_noState_usesNotification() {
+        val desc = "Notification Action"
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        whenever(controller.playbackState).thenReturn(null)
+
+        val notifWithAction = SbnBuilder().run {
+            setPkg(PACKAGE_NAME)
+            modifyNotification(context).also {
+                it.setSmallIcon(android.R.drawable.ic_media_pause)
+                it.setStyle(MediaStyle().apply { setMediaSession(session.sessionToken) })
+                it.addAction(android.R.drawable.ic_media_play, desc, null)
+            }
+            build()
+        }
+        mediaDataManager.onNotificationAdded(KEY, notifWithAction)
+
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNull()
+        assertThat(mediaDataCaptor.value!!.actions).hasSize(1)
+        assertThat(mediaDataCaptor.value!!.actions[0]!!.contentDescription).isEqualTo(desc)
+    }
+
+    @Test
+    fun testPlaybackActions_hasPrevNext() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY or
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS or
+                PlaybackState.ACTION_SKIP_TO_NEXT
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+        actions.playOrPause!!.action!!.run()
+        verify(transportControls).play()
+
+        assertThat(actions.prevOrCustom).isNotNull()
+        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_prev))
+        actions.prevOrCustom!!.action!!.run()
+        verify(transportControls).skipToPrevious()
+
+        assertThat(actions.nextOrCustom).isNotNull()
+        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_next))
+        actions.nextOrCustom!!.action!!.run()
+        verify(transportControls).skipToNext()
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+    }
+
+    @Test
+    fun testPlaybackActions_noPrevNext_usesCustom() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4", "custom 5")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+
+        assertThat(actions.prevOrCustom).isNotNull()
+        assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.nextOrCustom).isNotNull()
+        assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo(customDesc[1])
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[2])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[3])
+    }
+
+    @Test
+    fun testPlaybackActions_reservedSpace() {
+        val customDesc = arrayOf("custom 1", "custom 2", "custom 3", "custom 4")
+        whenever(mediaFlags.areMediaSessionActionsEnabled()).thenReturn(true)
+        val stateActions = PlaybackState.ACTION_PLAY
+        val stateBuilder = PlaybackState.Builder()
+                .setActions(stateActions)
+        customDesc.forEach {
+            stateBuilder.addCustomAction("action: $it", it, android.R.drawable.ic_media_pause)
+        }
+        val extras = Bundle().apply {
+            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV, true)
+            putBoolean(MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT, true)
+        }
+        whenever(controller.playbackState).thenReturn(stateBuilder.build())
+        whenever(controller.extras).thenReturn(extras)
+
+        mediaDataManager.onNotificationAdded(KEY, mediaNotification)
+        assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
+        assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
+        verify(listener).onMediaDataLoaded(eq(KEY), eq(null), capture(mediaDataCaptor), eq(true),
+                eq(0))
+
+        assertThat(mediaDataCaptor.value!!.semanticActions).isNotNull()
+        val actions = mediaDataCaptor.value!!.semanticActions!!
+
+        assertThat(actions.playOrPause).isNotNull()
+        assertThat(actions.playOrPause!!.contentDescription).isEqualTo(
+                context.getString(R.string.controls_media_button_play))
+
+        assertThat(actions.prevOrCustom).isNull()
+        assertThat(actions.nextOrCustom).isNull()
+
+        assertThat(actions.startCustom).isNotNull()
+        assertThat(actions.startCustom!!.contentDescription).isEqualTo(customDesc[0])
+
+        assertThat(actions.endCustom).isNotNull()
+        assertThat(actions.endCustom!!.contentDescription).isEqualTo(customDesc[1])
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7dadbad..3d59497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -102,9 +102,24 @@
         // Create a media sesssion and notification for testing.
         session = MediaSession(context, SESSION_KEY)
 
-        mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-            emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
-            device = null, active = true, resumeAction = null)
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = 0,
+                app = PACKAGE,
+                appIcon = null,
+                artist = null,
+                song = SESSION_TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null)
+
         whenever(controllerFactory.create(session.sessionToken))
                 .thenReturn(controller)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index bf87a4a..a3ffb2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -22,6 +22,7 @@
 import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -82,6 +83,8 @@
     private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock
     private lateinit var configurationController: ConfigurationController
+    @Mock
+    private lateinit var uniqueObjectHostView: UniqueObjectHostView
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @Captor
@@ -94,6 +97,8 @@
 
     @Before
     fun setup() {
+        context.getOrCreateTestableResources().addOverride(
+                R.bool.config_use_split_notification_shade, false)
         mediaFrame = FrameLayout(context)
         `when`(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
         mediaHiearchyManager = MediaHierarchyManager(
@@ -124,7 +129,7 @@
     private fun setupHost(host: MediaHost, location: Int) {
         `when`(host.location).thenReturn(location)
         `when`(host.currentBounds).thenReturn(Rect())
-        `when`(host.hostView).thenReturn(UniqueObjectHostView(context))
+        `when`(host.hostView).thenReturn(uniqueObjectHostView)
         `when`(host.visible).thenReturn(true)
         mediaHiearchyManager.register(host)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
index 421f9be..ceeb0db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaPlayerDataTest.kt
@@ -163,8 +163,27 @@
         isPlaying: Boolean?,
         location: Int,
         resumption: Boolean
-    ) =
-        MediaData(0, false, 0, app, null, null, null, null, emptyList(), emptyList<Int>(),
-            "package:" + app, null, null, null, true, null, location, resumption, "key:" + app,
-            false, isPlaying)
+    ) = MediaData(
+        userId = 0,
+        initialized = false,
+        backgroundColor = 0,
+        app = app,
+        appIcon = null,
+        artist = null,
+        song = null,
+        artwork = null,
+        actions = emptyList(),
+        actionsToShowInCompact = emptyList(),
+        packageName = "package: $app",
+        token = null,
+        clickIntent = null,
+        device = null,
+        active = true,
+        resumeAction = null,
+        playbackLocation = location,
+        resumption = resumption,
+        notificationKey = "key: $app",
+        hasCheckedForResume = false,
+        isPlaying = isPlaying
+    )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index de2235d..8c2fed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -95,13 +95,26 @@
             setPlaybackState(playbackBuilder.build())
         }
         session.setActive(true)
-        mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-            emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
-            device = null, active = true, resumeAction = null)
 
-        resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
-                emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
-                device = null, active = false, resumeAction = null, resumption = true)
+        mediaData = MediaData(
+                userId = USER_ID,
+                initialized = true,
+                backgroundColor = 0,
+                app = PACKAGE,
+                appIcon = null,
+                artist = null,
+                song = SESSION_TITLE,
+                artwork = null,
+                actions = emptyList(),
+                actionsToShowInCompact = emptyList(),
+                packageName = PACKAGE,
+                token = session.sessionToken,
+                clickIntent = null,
+                device = null,
+                active = true,
+                resumeAction = null)
+
+        resumeData = mediaData.copy(token = null, active = false, resumption = true)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 4dac6d5..9f542f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -66,6 +67,7 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
     private MediaOutputController mMediaOutputController;
@@ -79,7 +81,7 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -169,7 +171,7 @@
     class MediaOutputBaseDialogImpl extends MediaOutputBaseDialog {
 
         MediaOutputBaseDialogImpl(Context context, MediaOutputController mediaOutputController) {
-            super(context, mediaOutputController);
+            super(context, mediaOutputController, mDialogManager);
 
             mAdapter = mMediaOutputBaseAdapter;
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index d71d98e..a84a803 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -55,6 +55,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -94,6 +95,7 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private Context mSpyContext;
     private MediaOutputController mMediaOutputController;
@@ -116,7 +118,7 @@
 
         mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -160,7 +162,7 @@
     public void start_withoutPackageName_verifyMediaControllerInit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
 
         mMediaOutputController.start(mCb);
 
@@ -181,7 +183,7 @@
     public void stop_withoutPackageName_verifyMediaControllerDeinit() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
 
         mMediaOutputController.start(mCb);
 
@@ -452,7 +454,7 @@
     public void getNotificationLargeIcon_withoutPackageName_returnsNull() {
         mMediaOutputController = new MediaOutputController(mSpyContext, null, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 8a3ea56..ada8d35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -67,6 +68,7 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputDialog mMediaOutputDialog;
     private MediaOutputController mMediaOutputController;
@@ -76,10 +78,10 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false,
-                mMediaOutputController, mUiEventLogger);
+                mMediaOutputController, mUiEventLogger, mDialogManager);
         mMediaOutputDialog.show();
 
         when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice);
@@ -125,7 +127,7 @@
     // and verify if the calling times increases.
     public void onCreate_ShouldLogVisibility() {
         MediaOutputDialog testDialog = new MediaOutputDialog(mContext, false,
-                mMediaOutputController, mUiEventLogger);
+                mMediaOutputController, mUiEventLogger, mDialogManager);
         testDialog.show();
 
         testDialog.dismissDialog();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index e8cd6c8..b114452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -66,6 +67,7 @@
             mock(NotificationEntryManager.class);
     private final UiEventLogger mUiEventLogger = mock(UiEventLogger.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private final SystemUIDialogManager mDialogManager = mock(SystemUIDialogManager.class);
 
     private MediaOutputGroupDialog mMediaOutputGroupDialog;
     private MediaOutputController mMediaOutputController;
@@ -75,10 +77,10 @@
     public void setUp() {
         mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE, false,
                 mMediaSessionManager, mLocalBluetoothManager, mShadeController, mStarter,
-                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator);
+                mNotificationEntryManager, mUiEventLogger, mDialogLaunchAnimator, mDialogManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false,
-                mMediaOutputController);
+                mMediaOutputController, mDialogManager);
         mMediaOutputGroupDialog.show();
         when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
index 923f018..8749917 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttChipControllerTest.kt
@@ -23,10 +23,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.commandline.Command
-import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.SettableFuture
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
@@ -35,18 +36,16 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
-import java.util.concurrent.Executor
 
 @SmallTest
 class MediaTttChipControllerTest : SysuiTestCase() {
 
-    private lateinit var mediaTttChipController: MediaTttChipController
+    private lateinit var fakeMainClock: FakeSystemClock
+    private lateinit var fakeMainExecutor: FakeExecutor
+    private lateinit var fakeBackgroundClock: FakeSystemClock
+    private lateinit var fakeBackgroundExecutor: FakeExecutor
 
-    private val inlineExecutor = Executor { command -> command.run() }
-    private val commandRegistry = CommandRegistry(context, inlineExecutor)
-    private val pw = PrintWriter(StringWriter())
+    private lateinit var mediaTttChipController: MediaTttChipController
 
     @Mock
     private lateinit var windowManager: WindowManager
@@ -54,166 +53,212 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        mediaTttChipController = MediaTttChipController(commandRegistry, context, windowManager)
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun constructor_addCommmandAlreadyRegistered() {
-        // Since creating the chip controller should automatically register the add command, it
-        // should throw when registering it again.
-        commandRegistry.registerCommand(
-            MediaTttChipController.ADD_CHIP_COMMAND_TAG
-        ) { EmptyCommand() }
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun constructor_removeCommmandAlreadyRegistered() {
-        // Since creating the chip controller should automatically register the remove command, it
-        // should throw when registering it again.
-        commandRegistry.registerCommand(
-            MediaTttChipController.REMOVE_CHIP_COMMAND_TAG
-        ) { EmptyCommand() }
+        fakeMainClock = FakeSystemClock()
+        fakeMainExecutor = FakeExecutor(fakeMainClock)
+        fakeBackgroundClock = FakeSystemClock()
+        fakeBackgroundExecutor = FakeExecutor(fakeBackgroundClock)
+        mediaTttChipController = MediaTttChipController(
+            context, windowManager, fakeMainExecutor, fakeBackgroundExecutor
+        )
     }
 
     @Test
-    fun addChipCommand_chipAdded() {
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+    fun displayChip_chipAdded() {
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
 
         verify(windowManager).addView(any(), any())
     }
 
     @Test
-    fun addChipCommand_twice_chipNotAddedTwice() {
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+    fun displayChip_twice_chipNotAddedTwice() {
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
         reset(windowManager)
 
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
         verify(windowManager, never()).addView(any(), any())
     }
 
     @Test
-    fun removeChipCommand_chipRemoved() {
+    fun removeChip_chipRemoved() {
         // First, add the chip
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
 
         // Then, remove it
-        commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
+        mediaTttChipController.removeChip()
 
         verify(windowManager).removeView(any())
     }
 
     @Test
-    fun removeChipCommand_noAdd_viewNotRemoved() {
-        commandRegistry.onShellCommand(pw, arrayOf(MediaTttChipController.REMOVE_CHIP_COMMAND_TAG))
+    fun removeChip_noAdd_viewNotRemoved() {
+        mediaTttChipController.removeChip()
 
         verify(windowManager, never()).removeView(any())
     }
 
     @Test
     fun moveCloserToTransfer_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
 
         val chipView = getChipView()
         assertThat(chipView.getChipText()).contains(DEVICE_NAME)
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
     }
 
     @Test
-    fun transferInitiated_chipTextContainsDeviceName_loadingIcon_noUndo() {
-        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+    fun transferInitiated_futureNotResolvedYet_loadingIcon_noUndo() {
+        val future: SettableFuture<Runnable?> = SettableFuture.create()
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
 
+        // Don't resolve the future in any way and don't run our executors
+
+        // Assert we're still in the loading state
         val chipView = getChipView()
         assertThat(chipView.getChipText()).contains(DEVICE_NAME)
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
-        assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
     }
 
     @Test
-    fun transferSucceeded_chipTextContainsDeviceName_noLoadingIcon_undo() {
-        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+    fun transferInitiated_futureResolvedSuccessfully_switchesToTransferSucceeded() {
+        val future: SettableFuture<Runnable?> = SettableFuture.create()
+        val undoRunnable = Runnable { }
+
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+
+        future.set(undoRunnable)
+        fakeBackgroundExecutor.advanceClockToLast()
+        fakeBackgroundExecutor.runAllReady()
+        fakeMainExecutor.advanceClockToLast()
+        val numRun = fakeMainExecutor.runAllReady()
+
+        // Assert we ran the future callback
+        assertThat(numRun).isEqualTo(1)
+        // Assert that we've moved to the successful state
+        val chipView = getChipView()
+        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+    }
+
+    @Test
+    fun transferInitiated_futureCancelled_chipRemoved() {
+        val future: SettableFuture<Runnable?> = SettableFuture.create()
+
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+
+        future.cancel(true)
+        fakeBackgroundExecutor.advanceClockToLast()
+        fakeBackgroundExecutor.runAllReady()
+        fakeMainExecutor.advanceClockToLast()
+        val numRun = fakeMainExecutor.runAllReady()
+
+        // Assert we ran the future callback
+        assertThat(numRun).isEqualTo(1)
+        // Assert that we've hidden the chip
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferInitiated_futureNotResolvedAfterTimeout_chipRemoved() {
+        val future: SettableFuture<Runnable?> = SettableFuture.create()
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, future))
+
+        // We won't set anything on the future, but we will still run the executors so that we're
+        // waiting on the future resolving. If we have a bug in our code, then this test will time
+        // out because we're waiting on the future indefinitely.
+        fakeBackgroundExecutor.advanceClockToLast()
+        fakeBackgroundExecutor.runAllReady()
+        fakeMainExecutor.advanceClockToLast()
+        val numRun = fakeMainExecutor.runAllReady()
+
+        // Assert we eventually decide to not wait for the future anymore
+        assertThat(numRun).isEqualTo(1)
+        // Assert we've hidden the chip
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun transferSucceededNullUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_noUndo() {
+        mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, undoRunnable = null))
 
         val chipView = getChipView()
         assertThat(chipView.getChipText()).contains(DEVICE_NAME)
         assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
-        assertThat(chipView.getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.GONE)
+    }
+
+    @Test
+    fun transferSucceededWithUndoRunnable_chipTextContainsDeviceName_noLoadingIcon_undoWithClick() {
+        mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
+
+        val chipView = getChipView()
+        assertThat(chipView.getChipText()).contains(DEVICE_NAME)
+        assertThat(chipView.getLoadingIconVisibility()).isEqualTo(View.GONE)
+        assertThat(chipView.getUndoButton().visibility).isEqualTo(View.VISIBLE)
+        assertThat(chipView.getUndoButton().hasOnClickListeners()).isTrue()
+    }
+
+    @Test
+    fun transferSucceededWithUndoRunnable_undoButtonClickRunsRunnable() {
+        var runnableRun = false
+        val runnable = Runnable { runnableRun = true }
+
+        mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME, runnable))
+        getChipView().getUndoButton().performClick()
+
+        assertThat(runnableRun).isTrue()
     }
 
     @Test
     fun changeFromCloserToTransferToTransferInitiated_loadingIconAppears() {
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
-        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
 
         assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.VISIBLE)
     }
 
     @Test
     fun changeFromTransferInitiatedToTransferSucceeded_loadingIconDisappears() {
-        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
-        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
+        mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
 
         assertThat(getChipView().getLoadingIconVisibility()).isEqualTo(View.GONE)
     }
 
     @Test
     fun changeFromTransferInitiatedToTransferSucceeded_undoButtonAppears() {
-        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
-        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+        mediaTttChipController.displayChip(TransferInitiated(DEVICE_NAME, TEST_FUTURE))
+        mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME) { })
 
-        assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.VISIBLE)
+        assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.VISIBLE)
     }
 
     @Test
     fun changeFromTransferSucceededToMoveCloser_undoButtonDisappears() {
-        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
-        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+        mediaTttChipController.displayChip(TransferSucceeded(DEVICE_NAME))
+        mediaTttChipController.displayChip(MoveCloserToTransfer(DEVICE_NAME))
 
-        assertThat(getChipView().getUndoButtonVisibility()).isEqualTo(View.GONE)
+        assertThat(getChipView().getUndoButton().visibility).isEqualTo(View.GONE)
     }
 
-    private fun getMoveCloserToTransferCommand(): Array<String> =
-        arrayOf(
-            MediaTttChipController.ADD_CHIP_COMMAND_TAG,
-            DEVICE_NAME,
-            MediaTttChipController.ChipType.MOVE_CLOSER_TO_TRANSFER.name
-        )
-
-    private fun getTransferInitiatedCommand(): Array<String> =
-        arrayOf(
-            MediaTttChipController.ADD_CHIP_COMMAND_TAG,
-            DEVICE_NAME,
-            MediaTttChipController.ChipType.TRANSFER_INITIATED.name
-        )
-
-    private fun getTransferSucceededCommand(): Array<String> =
-        arrayOf(
-            MediaTttChipController.ADD_CHIP_COMMAND_TAG,
-            DEVICE_NAME,
-            MediaTttChipController.ChipType.TRANSFER_SUCCEEDED.name
-        )
-
     private fun LinearLayout.getChipText(): String =
         (this.requireViewById<TextView>(R.id.text)).text as String
 
     private fun LinearLayout.getLoadingIconVisibility(): Int =
         this.requireViewById<View>(R.id.loading).visibility
 
-    private fun LinearLayout.getUndoButtonVisibility(): Int =
-        this.requireViewById<View>(R.id.undo).visibility
+    private fun LinearLayout.getUndoButton(): View = this.requireViewById(R.id.undo)
 
     private fun getChipView(): LinearLayout {
         val viewCaptor = ArgumentCaptor.forClass(View::class.java)
         verify(windowManager).addView(viewCaptor.capture(), any())
         return viewCaptor.value as LinearLayout
     }
-
-    class EmptyCommand : Command {
-        override fun execute(pw: PrintWriter, args: List<String>) {
-        }
-
-        override fun help(pw: PrintWriter) {
-        }
-    }
 }
 
 private const val DEVICE_NAME = "My Tablet"
+// Use a settable future that hasn't yet been set so that we don't immediately switch to the success
+// state.
+private val TEST_FUTURE: SettableFuture<Runnable?> = SettableFuture.create()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
new file mode 100644
index 0000000..9b2d3eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.media.taptotransfer
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.commandline.Command
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.util.concurrent.Executor
+
+@SmallTest
+class MediaTttCommandLineHelperTest : SysuiTestCase() {
+
+    private val inlineExecutor = Executor { command -> command.run() }
+    private val commandRegistry = CommandRegistry(context, inlineExecutor)
+    private val pw = PrintWriter(StringWriter())
+
+    private lateinit var mediaTttCommandLineHelper: MediaTttCommandLineHelper
+
+    @Mock
+    private lateinit var mediaTttChipController: MediaTttChipController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mediaTttCommandLineHelper =
+            MediaTttCommandLineHelper(
+                commandRegistry, mediaTttChipController, FakeExecutor(FakeSystemClock())
+            )
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun constructor_addCommandAlreadyRegistered() {
+        // Since creating the chip controller should automatically register the add command, it
+        // should throw when registering it again.
+        commandRegistry.registerCommand(
+            ADD_CHIP_COMMAND_TAG
+        ) { EmptyCommand() }
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun constructor_removeCommandAlreadyRegistered() {
+        // Since creating the chip controller should automatically register the remove command, it
+        // should throw when registering it again.
+        commandRegistry.registerCommand(
+            REMOVE_CHIP_COMMAND_TAG
+        ) { EmptyCommand() }
+    }
+
+    @Test
+    fun moveCloserToTransfer_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getMoveCloserToTransferCommand())
+
+        verify(mediaTttChipController).displayChip(any(MoveCloserToTransfer::class.java))
+    }
+
+    @Test
+    fun transferInitiated_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getTransferInitiatedCommand())
+
+        verify(mediaTttChipController).displayChip(any(TransferInitiated::class.java))
+    }
+
+    @Test
+    fun transferSucceeded_chipDisplayWithCorrectState() {
+        commandRegistry.onShellCommand(pw, getTransferSucceededCommand())
+
+        verify(mediaTttChipController).displayChip(any(TransferSucceeded::class.java))
+    }
+
+    @Test
+    fun removeCommand_chipRemoved() {
+        commandRegistry.onShellCommand(pw, arrayOf(REMOVE_CHIP_COMMAND_TAG))
+
+        verify(mediaTttChipController).removeChip()
+    }
+
+    private fun getMoveCloserToTransferCommand(): Array<String> =
+        arrayOf(
+            ADD_CHIP_COMMAND_TAG,
+            DEVICE_NAME,
+            MOVE_CLOSER_TO_TRANSFER_COMMAND_NAME
+        )
+
+    private fun getTransferInitiatedCommand(): Array<String> =
+        arrayOf(
+            ADD_CHIP_COMMAND_TAG,
+            DEVICE_NAME,
+            TRANSFER_INITIATED_COMMAND_NAME
+        )
+
+    private fun getTransferSucceededCommand(): Array<String> =
+        arrayOf(
+            ADD_CHIP_COMMAND_TAG,
+            DEVICE_NAME,
+            TRANSFER_SUCCEEDED_COMMAND_NAME
+        )
+
+    class EmptyCommand : Command {
+        override fun execute(pw: PrintWriter, args: List<String>) {
+        }
+
+        override fun help(pw: PrintWriter) {
+        }
+    }
+}
+
+private const val DEVICE_NAME = "My Tablet"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
index 9d2541c..3e8e874 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.wm.shell.pip.Pip;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,6 +56,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.Optional;
+
 /** atest NavigationBarControllerTest */
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -88,7 +91,8 @@
                         mNavigationBarFactory,
                         mock(DumpManager.class),
                         mock(AutoHideController.class),
-                        mock(LightBarController.class)));
+                        mock(LightBarController.class),
+                        Optional.of(mock(Pip.class))));
         initializeNavigationBars();
     }
 
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 c1562c1..6f4e619 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -185,8 +185,8 @@
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         CommandQueue commandQueue = new CommandQueue(context);
         return new QSFragment(
-                new RemoteInputQuickSettingsDisabler(context, mock(ConfigurationController.class),
-                        commandQueue),
+                new RemoteInputQuickSettingsDisabler(context, commandQueue,
+                        mock(ConfigurationController.class)),
                 mock(QSTileHost.class),
                 mock(StatusBarStateController.class),
                 commandQueue,
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 24e47c5..bd4bfff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -58,10 +58,9 @@
     }
 
     private QSPanelControllerBase.TileRecord createTileRecord() {
-        QSPanelControllerBase.TileRecord tileRecord = new QSPanelControllerBase.TileRecord();
-        tileRecord.tile = mock(QSTile.class);
-        tileRecord.tileView = spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext)));
-        return tileRecord;
+        return new QSPanelControllerBase.TileRecord(
+                mock(QSTile.class),
+                spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext))));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
new file mode 100644
index 0000000..af33daf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.qs.tileimpl
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.tiles.AirplaneModeTile
+import com.android.systemui.qs.tiles.AlarmTile
+import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.BluetoothTile
+import com.android.systemui.qs.tiles.CameraToggleTile
+import com.android.systemui.qs.tiles.CastTile
+import com.android.systemui.qs.tiles.CellularTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.DataSaverTile
+import com.android.systemui.qs.tiles.DeviceControlsTile
+import com.android.systemui.qs.tiles.DndTile
+import com.android.systemui.qs.tiles.FgsManagerTile
+import com.android.systemui.qs.tiles.FlashlightTile
+import com.android.systemui.qs.tiles.HotspotTile
+import com.android.systemui.qs.tiles.InternetTile
+import com.android.systemui.qs.tiles.LocationTile
+import com.android.systemui.qs.tiles.MicrophoneToggleTile
+import com.android.systemui.qs.tiles.NfcTile
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.QRCodeScannerTile
+import com.android.systemui.qs.tiles.QuickAccessWalletTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.qs.tiles.RotationLockTile
+import com.android.systemui.qs.tiles.ScreenRecordTile
+import com.android.systemui.qs.tiles.UiModeNightTile
+import com.android.systemui.qs.tiles.UserTile
+import com.android.systemui.qs.tiles.WifiTile
+import com.android.systemui.qs.tiles.WorkModeTile
+import com.android.systemui.util.leak.GarbageMonitor
+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.inOrder
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+private val specMap = mapOf(
+        "wifi" to WifiTile::class.java,
+        "internet" to InternetTile::class.java,
+        "bt" to BluetoothTile::class.java,
+        "cell" to CellularTile::class.java,
+        "dnd" to DndTile::class.java,
+        "inversion" to ColorInversionTile::class.java,
+        "airplane" to AirplaneModeTile::class.java,
+        "work" to WorkModeTile::class.java,
+        "rotation" to RotationLockTile::class.java,
+        "flashlight" to FlashlightTile::class.java,
+        "location" to LocationTile::class.java,
+        "cast" to CastTile::class.java,
+        "hotspot" to HotspotTile::class.java,
+        "user" to UserTile::class.java,
+        "battery" to BatterySaverTile::class.java,
+        "saver" to DataSaverTile::class.java,
+        "night" to NightDisplayTile::class.java,
+        "nfc" to NfcTile::class.java,
+        "dark" to UiModeNightTile::class.java,
+        "screenrecord" to ScreenRecordTile::class.java,
+        "reduce_brightness" to ReduceBrightColorsTile::class.java,
+        "cameratoggle" to CameraToggleTile::class.java,
+        "mictoggle" to MicrophoneToggleTile::class.java,
+        "controls" to DeviceControlsTile::class.java,
+        "alarm" to AlarmTile::class.java,
+        "wallet" to QuickAccessWalletTile::class.java,
+        "qr_code_scanner" to QRCodeScannerTile::class.java,
+        "onehanded" to OneHandedModeTile::class.java,
+        "fgsmanager" to FgsManagerTile::class.java
+)
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class QSFactoryImplTest : SysuiTestCase() {
+
+    @Mock private lateinit var qsHost: QSHost
+    @Mock(answer = Answers.RETURNS_SELF) private lateinit var customTileBuilder: CustomTile.Builder
+    @Mock private lateinit var customTile: CustomTile
+
+    @Mock private lateinit var wifiTile: WifiTile
+    @Mock private lateinit var internetTile: InternetTile
+    @Mock private lateinit var bluetoothTile: BluetoothTile
+    @Mock private lateinit var cellularTile: CellularTile
+    @Mock private lateinit var dndTile: DndTile
+    @Mock private lateinit var colorInversionTile: ColorInversionTile
+    @Mock private lateinit var airplaneTile: AirplaneModeTile
+    @Mock private lateinit var workTile: WorkModeTile
+    @Mock private lateinit var rotationTile: RotationLockTile
+    @Mock private lateinit var flashlightTile: FlashlightTile
+    @Mock private lateinit var locationTile: LocationTile
+    @Mock private lateinit var castTile: CastTile
+    @Mock private lateinit var hotspotTile: HotspotTile
+    @Mock private lateinit var userTile: UserTile
+    @Mock private lateinit var batterySaverTile: BatterySaverTile
+    @Mock private lateinit var dataSaverTile: DataSaverTile
+    @Mock private lateinit var nightDisplayTile: NightDisplayTile
+    @Mock private lateinit var nfcTile: NfcTile
+    @Mock private lateinit var memoryTile: GarbageMonitor.MemoryTile
+    @Mock private lateinit var darkModeTile: UiModeNightTile
+    @Mock private lateinit var screenRecordTile: ScreenRecordTile
+    @Mock private lateinit var reduceBrightColorsTile: ReduceBrightColorsTile
+    @Mock private lateinit var cameraToggleTile: CameraToggleTile
+    @Mock private lateinit var microphoneToggleTile: MicrophoneToggleTile
+    @Mock private lateinit var deviceControlsTile: DeviceControlsTile
+    @Mock private lateinit var alarmTile: AlarmTile
+    @Mock private lateinit var quickAccessWalletTile: QuickAccessWalletTile
+    @Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile
+    @Mock private lateinit var oneHandedModeTile: OneHandedModeTile
+    @Mock private lateinit var fgsManagerTile: FgsManagerTile
+
+    private lateinit var factory: QSFactoryImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(qsHost.context).thenReturn(mContext)
+        whenever(qsHost.userContext).thenReturn(mContext)
+        whenever(customTileBuilder.build()).thenReturn(customTile)
+
+        factory = QSFactoryImpl(
+                { qsHost },
+                { customTileBuilder },
+                { wifiTile },
+                { internetTile },
+                { bluetoothTile },
+                { cellularTile },
+                { dndTile },
+                { colorInversionTile },
+                { airplaneTile },
+                { workTile },
+                { rotationTile },
+                { flashlightTile },
+                { locationTile },
+                { castTile },
+                { hotspotTile },
+                { userTile },
+                { batterySaverTile },
+                { dataSaverTile },
+                { nightDisplayTile },
+                { nfcTile },
+                { memoryTile },
+                { darkModeTile },
+                { screenRecordTile },
+                { reduceBrightColorsTile },
+                { cameraToggleTile },
+                { microphoneToggleTile },
+                { deviceControlsTile },
+                { alarmTile },
+                { quickAccessWalletTile },
+                { qrCodeScannerTile },
+                { oneHandedModeTile },
+                { fgsManagerTile }
+        )
+        // When adding/removing tiles, fix also [specMap]
+    }
+
+    @Test
+    fun testCorrectTileClassStock() {
+        specMap.forEach { spec, klazz ->
+            assertThat(factory.createTile(spec)).isInstanceOf(klazz)
+        }
+    }
+
+    @Test
+    fun testCustomTileClass() {
+        val customSpec = CustomTile.toSpec(ComponentName("test", "test"))
+        assertThat(factory.createTile(customSpec)).isInstanceOf(CustomTile::class.java)
+    }
+
+    @Test
+    fun testBadTileNull() {
+        assertThat(factory.createTile("-432~")).isNull()
+    }
+
+    @Test
+    fun testTileInitializedAndStale() {
+        specMap.forEach { spec, _ ->
+            val tile = factory.createTile(spec) as QSTileImpl<*>
+            val inOrder = inOrder(tile)
+            inOrder.verify(tile).initialize()
+            inOrder.verify(tile).postStale()
+        }
+
+        val customSpec = CustomTile.toSpec(ComponentName("test", "test"))
+        val tile = factory.createTile(customSpec) as QSTileImpl<*>
+        val inOrder = inOrder(tile)
+        inOrder.verify(tile).initialize()
+        inOrder.verify(tile).postStale()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
index 77946cf..d3bb241 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -1,5 +1,7 @@
 package com.android.systemui.qs.tiles.dialog;
 
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -83,7 +85,7 @@
 
     @Test
     public void getItemCount_returnWifiEntriesCount() {
-        for (int i = 0; i < InternetDialogController.MAX_WIFI_ENTRY_COUNT; i++) {
+        for (int i = 0; i < MAX_WIFI_ENTRY_COUNT; i++) {
             mInternetAdapter.setWifiEntries(mWifiEntries, i /* wifiEntriesCount */);
 
             assertThat(mInternetAdapter.getItemCount()).isEqualTo(i);
@@ -141,6 +143,60 @@
     }
 
     @Test
+    public void setWifiEntries_wifiCountLessThenMaxCount_setWifiCount() {
+        final int wifiCount = MAX_WIFI_ENTRY_COUNT - 1;
+        mInternetAdapter.mMaxEntriesCount = MAX_WIFI_ENTRY_COUNT;
+
+        mInternetAdapter.setWifiEntries(mWifiEntries, wifiCount);
+
+        assertThat(mInternetAdapter.mWifiEntriesCount).isEqualTo(wifiCount);
+    }
+
+    @Test
+    public void setWifiEntries_wifiCountGreaterThenMaxCount_setMaxCount() {
+        final int wifiCount = MAX_WIFI_ENTRY_COUNT;
+        mInternetAdapter.mMaxEntriesCount = MAX_WIFI_ENTRY_COUNT - 1;
+
+        mInternetAdapter.setWifiEntries(mWifiEntries, wifiCount);
+
+        assertThat(mInternetAdapter.mWifiEntriesCount).isEqualTo(mInternetAdapter.mMaxEntriesCount);
+    }
+
+    @Test
+    public void setMaxEntriesCount_maxCountLessThenZero_doNothing() {
+        mInternetAdapter.mMaxEntriesCount = MAX_WIFI_ENTRY_COUNT;
+        final int maxCount = -1;
+
+        mInternetAdapter.setMaxEntriesCount(maxCount);
+
+        assertThat(mInternetAdapter.mMaxEntriesCount).isEqualTo(MAX_WIFI_ENTRY_COUNT);
+    }
+
+    @Test
+    public void setMaxEntriesCount_maxCountGreaterThenWifiCount_updateMaxCount() {
+        mInternetAdapter.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 2;
+        mInternetAdapter.mMaxEntriesCount = MAX_WIFI_ENTRY_COUNT;
+        final int maxCount = MAX_WIFI_ENTRY_COUNT - 1;
+
+        mInternetAdapter.setMaxEntriesCount(maxCount);
+
+        assertThat(mInternetAdapter.mWifiEntriesCount).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+        assertThat(mInternetAdapter.mMaxEntriesCount).isEqualTo(maxCount);
+    }
+
+    @Test
+    public void setMaxEntriesCount_maxCountLessThenWifiCount_updateBothCount() {
+        mInternetAdapter.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT;
+        mInternetAdapter.mMaxEntriesCount = MAX_WIFI_ENTRY_COUNT;
+        final int maxCount = MAX_WIFI_ENTRY_COUNT - 1;
+
+        mInternetAdapter.setMaxEntriesCount(maxCount);
+
+        assertThat(mInternetAdapter.mWifiEntriesCount).isEqualTo(maxCount);
+        assertThat(mInternetAdapter.mMaxEntriesCount).isEqualTo(maxCount);
+    }
+
+    @Test
     public void viewHolderUpdateEndIcon_wifiConnected_updateGearIcon() {
         mTestableResources.addOverride(GEAR_ICON_RES_ID, mGearIcon);
 
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 ca8903b..0d65541 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
@@ -1,5 +1,7 @@
 package com.android.systemui.qs.tiles.dialog;
 
+import static android.provider.Settings.Global.AIRPLANE_MODE_ON;
+
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
 import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
 
@@ -19,7 +21,6 @@
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
-import android.content.Context;
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
@@ -37,7 +38,6 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -47,7 +47,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -70,7 +69,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -78,8 +76,6 @@
 public class InternetDialogControllerTest extends SysuiTestCase {
 
     private static final int SUB_ID = 1;
-    private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
-    private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
 
     //SystemUIToast
     private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
@@ -142,7 +138,7 @@
     private DialogLaunchAnimator mDialogLaunchAnimator;
 
     private TestableResources mTestableResources;
-    private MockInternetDialogController mInternetDialogController;
+    private InternetDialogController mInternetDialogController;
     private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private List<WifiEntry> mAccessPoints = new ArrayList<>();
     private List<WifiEntry> mWifiEntries = new ArrayList<>();
@@ -163,14 +159,13 @@
         mAccessPoints.add(mWifiEntry1);
         when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
         when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
-        when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
         when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
             .thenReturn(mSystemUIToast);
         when(mSystemUIToast.getView()).thenReturn(mToastView);
         when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
         when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
 
-        mInternetDialogController = new MockInternetDialogController(mContext,
+        mInternetDialogController = new InternetDialogController(mContext,
                 mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
                 mSubscriptionManager, mTelephonyManager, mWifiManager,
                 mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
@@ -225,7 +220,7 @@
 
     @Test
     public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
-        mInternetDialogController.setAirplaneModeEnabled(true);
+        fakeAirplaneModeEnabled(true);
 
         assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
                 getResourcesString("airplane_mode")));
@@ -233,22 +228,30 @@
 
     @Test
     public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
 
         assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
                 getResourcesString("quick_settings_internet_label")));
     }
 
     @Test
-    public void getSubtitleText_withAirplaneModeOn_returnNull() {
-        mInternetDialogController.setAirplaneModeEnabled(true);
+    public void getSubtitleText_withApmOnAndWifiOff_returnWifiIsOff() {
+        fakeAirplaneModeEnabled(true);
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
 
-        assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("wifi_is_off"));
+
+        // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+        mInternetDialogController.mCanConfigWifi = false;
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isNotEqualTo(getResourcesString("wifi_is_off"));
     }
 
     @Test
     public void getSubtitleText_withWifiOff_returnWifiIsOff() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(false);
 
         assertThat(mInternetDialogController.getSubtitleText(false))
@@ -263,7 +266,7 @@
 
     @Test
     public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -280,7 +283,7 @@
     @Test
     public void getSubtitleText_withWifiEntry_returnTapToConnect() {
         // The preconditions WiFi Entries is already in setUp()
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
 
         assertThat(mInternetDialogController.getSubtitleText(false))
@@ -295,7 +298,7 @@
 
     @Test
     public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         when(mKeyguardStateController.isUnlocked()).thenReturn(false);
 
@@ -305,7 +308,7 @@
 
     @Test
     public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -319,7 +322,7 @@
 
     @Test
     public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
-        mInternetDialogController.setAirplaneModeEnabled(false);
+        fakeAirplaneModeEnabled(false);
         when(mWifiManager.isWifiEnabled()).thenReturn(true);
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
@@ -339,6 +342,17 @@
     }
 
     @Test
+    public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() {
+        fakeAirplaneModeEnabled(false);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+        when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true);
+
+        assertThat(mInternetDialogController.getSubtitleText(false))
+                .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+    }
+
+    @Test
     public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
         assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
     }
@@ -404,7 +418,7 @@
 
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
-        verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+        verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean());
     }
 
     @Test
@@ -413,27 +427,26 @@
 
         mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
 
-        verify(mInternetDialogCallback)
-                .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+        verify(mInternetDialogCallback).onAccessPointsChanged(null /* wifiEntries */,
+                null /* connectedEntry */, false /* hasMoreEntry */);
     }
 
     @Test
     public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
         mWifiEntries.clear();
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+                false /* hasMoreEntry */);
     }
 
     @Test
     public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mWifiEntry1);
 
@@ -441,14 +454,13 @@
 
         mWifiEntries.clear();
         mWifiEntries.add(mWifiEntry1);
-        verify(mInternetDialogCallback)
-                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+                null /* connectedEntry */, false /* hasMoreEntry */);
     }
 
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -457,13 +469,13 @@
 
         mWifiEntries.clear();
         mWifiEntries.add(mWifiEntry1);
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+                false /* hasMoreEntry */);
     }
 
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -474,13 +486,13 @@
         mWifiEntries.clear();
         mWifiEntries.add(mWifiEntry1);
         mWifiEntries.add(mWifiEntry2);
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+                false /* hasMoreEntry */);
     }
 
     @Test
     public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mConnectedEntry);
         mAccessPoints.add(mWifiEntry1);
@@ -492,52 +504,13 @@
         mWifiEntries.clear();
         mWifiEntries.add(mWifiEntry1);
         mWifiEntries.add(mWifiEntry2);
-        mWifiEntries.add(mWifiEntry3);
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
-
-        // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
-        reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
-
-        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
-
-        mWifiEntries.remove(mWifiEntry3);
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
-    }
-
-    @Test
-    public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
-        reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
-        mAccessPoints.clear();
-        mAccessPoints.add(mConnectedEntry);
-        mAccessPoints.add(mWifiEntry1);
-        mAccessPoints.add(mWifiEntry2);
-        mAccessPoints.add(mWifiEntry3);
-        mAccessPoints.add(mWifiEntry4);
-
-        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
-
-        mWifiEntries.clear();
-        mWifiEntries.add(mWifiEntry1);
-        mWifiEntries.add(mWifiEntry2);
-        mWifiEntries.add(mWifiEntry3);
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
-
-        // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
-        reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
-
-        mInternetDialogController.onAccessPointsChanged(mAccessPoints);
-
-        mWifiEntries.remove(mWifiEntry3);
-        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+                true /* hasMoreEntry */);
     }
 
     @Test
     public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(true);
         mAccessPoints.clear();
         mAccessPoints.add(mWifiEntry1);
         mAccessPoints.add(mWifiEntry2);
@@ -550,29 +523,56 @@
         mWifiEntries.add(mWifiEntry1);
         mWifiEntries.add(mWifiEntry2);
         mWifiEntries.add(mWifiEntry3);
-        mWifiEntries.add(mWifiEntry4);
-        verify(mInternetDialogCallback)
-                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+                null /* connectedEntry */, true /* hasMoreEntry */);
+    }
 
-        // If the Ethernet exists, then Wi-Fi entries will cut last one.
+    @Test
+    public void onAccessPointsChanged_wifiIsDefaultButNoInternetAccess_putIntoWifiEntries() {
         reset(mInternetDialogCallback);
-        mInternetDialogController.mHasEthernet = true;
+        mAccessPoints.clear();
+        when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry1.isDefaultNetwork()).thenReturn(true);
+        when(mWifiEntry1.hasInternetAccess()).thenReturn(false);
+        mAccessPoints.add(mWifiEntry1);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
-        mWifiEntries.remove(mWifiEntry4);
-        verify(mInternetDialogCallback)
-                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+        mWifiEntries.clear();
+        mWifiEntries.add(mWifiEntry1);
+        verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+                null /* connectedEntry */, false /* hasMoreEntry */);
+    }
 
-        // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
-        reset(mInternetDialogCallback);
-        mInternetDialogController.setAirplaneModeEnabled(false);
+    @Test
+    public void onAccessPointsChanged_connectedWifiNoInternetAccess_shouldSetListener() {
+        reset(mWifiEntry1);
+        mAccessPoints.clear();
+        when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry1.isDefaultNetwork()).thenReturn(true);
+        when(mWifiEntry1.hasInternetAccess()).thenReturn(false);
+        mAccessPoints.add(mWifiEntry1);
 
         mInternetDialogController.onAccessPointsChanged(mAccessPoints);
 
-        mWifiEntries.remove(mWifiEntry3);
-        verify(mInternetDialogCallback)
-                .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+        verify(mWifiEntry1).setListener(mInternetDialogController.mConnectedWifiInternetMonitor);
+    }
+
+    @Test
+    public void onUpdated_connectedWifiHasInternetAccess_shouldScanWifiAccessPoints() {
+        reset(mAccessPointController);
+        when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED);
+        when(mWifiEntry1.isDefaultNetwork()).thenReturn(true);
+        when(mWifiEntry1.hasInternetAccess()).thenReturn(false);
+        InternetDialogController.ConnectedWifiInternetMonitor mConnectedWifiInternetMonitor =
+                mInternetDialogController.mConnectedWifiInternetMonitor;
+        mConnectedWifiInternetMonitor.registerCallbackIfNeed(mWifiEntry1);
+
+        // When the hasInternetAccess() changed to true, and call back the onUpdated() function.
+        when(mWifiEntry1.hasInternetAccess()).thenReturn(true);
+        mConnectedWifiInternetMonitor.onUpdated();
+
+        verify(mAccessPointController).scanForAccessPoints();
     }
 
     @Test
@@ -641,38 +641,7 @@
                 mContext.getPackageName());
     }
 
-    private class MockInternetDialogController extends InternetDialogController {
-
-        private GlobalSettings mGlobalSettings;
-        private boolean mIsAirplaneModeOn;
-
-        MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
-                ActivityStarter starter, AccessPointController accessPointController,
-                SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
-                @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
-                @Main Handler handler, @Main Executor mainExecutor,
-                BroadcastDispatcher broadcastDispatcher,
-                KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
-                KeyguardStateController keyguardStateController, WindowManager windowManager,
-                ToastFactory toastFactory, Handler workerHandler,
-                CarrierConfigTracker carrierConfigTracker,
-                LocationController locationController,
-                DialogLaunchAnimator dialogLaunchAnimator) {
-            super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
-                    telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
-                    broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
-                    keyguardStateController, windowManager, toastFactory, workerHandler,
-                    carrierConfigTracker, locationController, dialogLaunchAnimator);
-            mGlobalSettings = globalSettings;
-        }
-
-        @Override
-        boolean isAirplaneModeEnabled() {
-            return mIsAirplaneModeOn;
-        }
-
-        public void setAirplaneModeEnabled(boolean enabled) {
-            mIsAirplaneModeOn = enabled;
-        }
+    private void fakeAirplaneModeEnabled(boolean enabled) {
+        when(mGlobalSettings.getInt(eq(AIRPLANE_MODE_ON), anyInt())).thenReturn(enabled ? 1 : 0);
     }
 }
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 0cf063f..c20e887 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
@@ -140,7 +140,7 @@
 
         mInternetDialog.updateDialog(true);
 
-        assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
@@ -312,6 +312,22 @@
         assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
         // Show a blank block to fix the dialog height even if there is no WiFi list
         assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mInternetAdapter).setMaxEntriesCount(3);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() {
+        // The precondition WiFi ON is already in setUp()
+        mInternetDialog.mConnectedWifiEntry = null;
+        mInternetDialog.mWifiEntriesCount = 1;
+
+        mInternetDialog.updateDialog(false);
+
+        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+        // Show a blank block to fix the dialog height even if there is no WiFi list
+        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mInternetAdapter).setMaxEntriesCount(3);
         assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
     }
 
@@ -325,29 +341,36 @@
         assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
         // Show a blank block to fix the dialog height even if there is no WiFi list
         assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mInternetAdapter).setMaxEntriesCount(2);
+        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
     }
 
     @Test
-    public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+    public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() {
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         mInternetDialog.mConnectedWifiEntry = null;
+        mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT;
+        mInternetDialog.mHasMoreWifiEntries = true;
 
         mInternetDialog.updateDialog(false);
 
         assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
         assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mInternetAdapter).setMaxEntriesCount(3);
         assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
     public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() {
         // The preconditions WiFi ON and WiFi entries are already in setUp()
+        mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1;
+        mInternetDialog.mHasMoreWifiEntries = true;
 
         mInternetDialog.updateDialog(false);
 
         assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mInternetAdapter).setMaxEntriesCount(2);
         assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
@@ -500,45 +523,46 @@
 
     @Test
     public void getWifiListMaxCount_returnCountCorrectly() {
-        // Ethernet, MobileData, ConnectedWiFi are all hidden.
+        // Both of the Ethernet, MobileData is hidden.
         // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT.
         setNetworkVisible(false, false, false);
 
         assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT);
 
-        // Only one of Ethernet, MobileData, ConnectedWiFi is displayed.
-        // Then the maximum count  is equal to MAX_WIFI_ENTRY_COUNT - 1.
-        setNetworkVisible(true, false, false);
-
-        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
-
-        setNetworkVisible(false, true, false);
-
-        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
-
+        // If the Connected Wi-Fi is displayed then reduce one of the Wi-Fi list max count.
         setNetworkVisible(false, false, true);
 
         assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
 
-        // Only one of Ethernet, MobileData, ConnectedWiFi is hidden.
-        // Then the maximum count  is equal to MAX_WIFI_ENTRY_COUNT - 2.
-        setNetworkVisible(true, true, false);
+        // Only one of Ethernet, MobileData is displayed.
+        // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT.
+        setNetworkVisible(true, false, false);
 
-        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT);
 
+        setNetworkVisible(false, true, false);
+
+        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT);
+
+        // If the Connected Wi-Fi is displayed then reduce one of the Wi-Fi list max count.
         setNetworkVisible(true, false, true);
 
-        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
 
         setNetworkVisible(false, true, true);
 
-        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
+        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
 
-        // Ethernet, MobileData, ConnectedWiFi are all displayed.
-        // Then the maximum count  is equal to MAX_WIFI_ENTRY_COUNT - 3.
+        // Both of Ethernet, MobileData, ConnectedWiFi is displayed.
+        // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 1.
+        setNetworkVisible(true, true, false);
+
+        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1);
+
+        // If the Connected Wi-Fi is displayed then reduce one of the Wi-Fi list max count.
         setNetworkVisible(true, true, true);
 
-        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 3);
+        assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2);
     }
 
     private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 7938511..89435ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -6,6 +6,7 @@
 import android.testing.TestableLooper.RunWithLooper
 import android.util.DisplayMetrics
 import com.android.systemui.ExpandHelper
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.media.MediaHierarchyManager
@@ -81,6 +82,8 @@
                 mDependency,
                 TestableLooper.get(this))
         row = helper.createRow()
+        context.getOrCreateTestableResources()
+                .addOverride(R.bool.config_use_split_notification_shade, false)
         transitionController = LockscreenShadeTransitionController(
             statusBarStateController = statusbarStateController,
             lockscreenGestureLogger = lockscreenGestureLogger,
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 afbe668..8c5f04f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -16,27 +16,30 @@
 
 package com.android.systemui.statusbar;
 
-import static org.mockito.ArgumentMatchers.any;
+import static com.google.common.truth.Truth.assertThat;
+
 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.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.os.Handler;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,7 +49,6 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
 public class NotificationListenerTest extends SysuiTestCase {
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
@@ -54,6 +56,8 @@
     @Mock private NotificationHandler mNotificationHandler;
     @Mock private NotificationManager mNotificationManager;
 
+    private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
     private NotificationListener mListener;
     private StatusBarNotification mSbn;
     private RankingMap mRanking = new RankingMap(new Ranking[0]);
@@ -65,7 +69,8 @@
         mListener = new NotificationListener(
                 mContext,
                 mNotificationManager,
-                new Handler(TestableLooper.get(this).getLooper()));
+                mFakeSystemClock,
+                mFakeExecutor);
         mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
                 new Notification(), UserHandle.CURRENT, null, 0);
 
@@ -75,23 +80,67 @@
     @Test
     public void testNotificationAddCallsAddNotification() {
         mListener.onNotificationPosted(mSbn, mRanking);
-        TestableLooper.get(this).processAllMessages();
+        mFakeExecutor.runAllReady();
         verify(mNotificationHandler).onNotificationPosted(mSbn, mRanking);
     }
 
     @Test
     public void testNotificationRemovalCallsRemoveNotification() {
         mListener.onNotificationRemoved(mSbn, mRanking);
-        TestableLooper.get(this).processAllMessages();
+        mFakeExecutor.runAllReady();
         verify(mNotificationHandler).onNotificationRemoved(eq(mSbn), eq(mRanking), anyInt());
     }
 
     @Test
     public void testRankingUpdateCallsNotificationRankingUpdate() {
         mListener.onNotificationRankingUpdate(mRanking);
-        TestableLooper.get(this).processAllMessages();
-        // RankingMap may be modified by plugins.
-        verify(mNotificationHandler).onNotificationRankingUpdate(any());
+        assertThat(mFakeExecutor.runAllReady()).isEqualTo(1);
+        verify(mNotificationHandler).onNotificationRankingUpdate(eq(mRanking));
+    }
+
+    @Test
+    public void testRankingUpdateMultipleTimesCallsNotificationRankingUpdateOnce() {
+        // GIVEN multiple notification ranking updates
+        RankingMap ranking1 = mock(RankingMap.class);
+        RankingMap ranking2 = mock(RankingMap.class);
+        RankingMap ranking3 = mock(RankingMap.class);
+        mListener.onNotificationRankingUpdate(ranking1);
+        mListener.onNotificationRankingUpdate(ranking2);
+        mListener.onNotificationRankingUpdate(ranking3);
+
+        // WHEN executor runs with multiple updates in the queue
+        assertThat(mFakeExecutor.numPending()).isEqualTo(3);
+        assertThat(mFakeExecutor.runAllReady()).isEqualTo(3);
+
+        // VERIFY that only the last ranking actually gets handled
+        verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking1));
+        verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking2));
+        verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
+        verifyNoMoreInteractions(mNotificationHandler);
+    }
+
+    @Test
+    public void testRankingUpdateWillCallAgainIfQueueIsSlow() {
+        // GIVEN multiple notification ranking updates
+        RankingMap ranking1 = mock(RankingMap.class);
+        RankingMap ranking2 = mock(RankingMap.class);
+        RankingMap ranking3 = mock(RankingMap.class);
+        mListener.onNotificationRankingUpdate(ranking1);
+        mListener.onNotificationRankingUpdate(ranking2);
+        mListener.onNotificationRankingUpdate(ranking3);
+
+        // WHEN executor runs with a 1-second gap between handling events 1 and 2
+        assertThat(mFakeExecutor.numPending()).isEqualTo(3);
+        assertThat(mFakeExecutor.runNextReady()).isTrue();
+        // delay a second, which empties the executor
+        mFakeSystemClock.advanceTime(1000);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+
+        // VERIFY that both event 2 and event 3 are called
+        verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking1));
+        verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking2));
+        verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
+        verifyNoMoreInteractions(mNotificationHandler);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
new file mode 100644
index 0000000..c7c8d04
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RunningFgsControllerTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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 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.Mockito.atLeastOnce;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.IActivityManager;
+import android.app.IForegroundServiceObserver;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.util.Pair;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.test.filters.MediumTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.RunningFgsController;
+import com.android.systemui.statusbar.policy.RunningFgsController.UserPackageTime;
+import com.android.systemui.statusbar.policy.RunningFgsControllerImpl;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.function.Consumer;
+
+@MediumTest
+@RunWith(AndroidTestingRunner.class)
+public class RunningFgsControllerTest extends SysuiTestCase {
+
+    private RunningFgsController mController;
+
+    private FakeSystemClock mSystemClock = new FakeSystemClock();
+    private FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
+    private TestCallback mCallback = new TestCallback();
+
+    @Mock
+    private IActivityManager mActivityManager;
+    @Mock
+    private Lifecycle mLifecycle;
+    @Mock
+    private LifecycleOwner mLifecycleOwner;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycle);
+        mController = new RunningFgsControllerImpl(mExecutor, mSystemClock, mActivityManager);
+    }
+
+    @Test
+    public void testInitRegistersListenerInImpl() throws RemoteException {
+        ((RunningFgsControllerImpl) mController).init();
+        verify(mActivityManager, times(1)).registerForegroundServiceObserver(any());
+    }
+
+    @Test
+    public void testAddCallbackCallsInitInImpl() {
+        verifyInitIsCalled(controller -> controller.addCallback(mCallback));
+    }
+
+    @Test
+    public void testRemoveCallbackCallsInitInImpl() {
+        verifyInitIsCalled(controller -> controller.removeCallback(mCallback));
+    }
+
+    @Test
+    public void testObserve1CallsInitInImpl() {
+        verifyInitIsCalled(controller -> controller.observe(mLifecycle, mCallback));
+    }
+
+    @Test
+    public void testObserve2CallsInitInImpl() {
+        verifyInitIsCalled(controller -> controller.observe(mLifecycleOwner, mCallback));
+    }
+
+    @Test
+    public void testGetPackagesWithFgsCallsInitInImpl() {
+        verifyInitIsCalled(controller -> controller.getPackagesWithFgs());
+    }
+
+    @Test
+    public void testStopFgsCallsInitInImpl() {
+        verifyInitIsCalled(controller -> controller.stopFgs(0, ""));
+    }
+
+    /**
+     * Tests that callbacks can be added
+     */
+    @Test
+    public void testAddCallback() throws RemoteException {
+        String testPackageName = "testPackageName";
+        int testUserId = 0;
+
+        IForegroundServiceObserver observer = prepareObserver();
+        mController.addCallback(mCallback);
+
+        observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true);
+
+        mExecutor.advanceClockToLast();
+        mExecutor.runAllReady();
+
+        assertEquals("Callback should have been invoked exactly once.",
+                1, mCallback.mInvocations.size());
+
+        List<UserPackageTime> userPackageTimes = mCallback.mInvocations.get(0);
+        assertEquals("There should have only been one package in callback. packages="
+                        + userPackageTimes,
+                1, userPackageTimes.size());
+
+        UserPackageTime upt = userPackageTimes.get(0);
+        assertEquals(testPackageName, upt.getPackageName());
+        assertEquals(testUserId, upt.getUserId());
+    }
+
+    /**
+     * Tests that callbacks can be removed. This test is only meaningful if
+     * {@link #testAddCallback()} can pass.
+     */
+    @Test
+    public void testRemoveCallback() throws RemoteException {
+        String testPackageName = "testPackageName";
+        int testUserId = 0;
+
+        IForegroundServiceObserver observer = prepareObserver();
+        mController.addCallback(mCallback);
+        mController.removeCallback(mCallback);
+
+        observer.onForegroundStateChanged(new Binder(), testPackageName, testUserId, true);
+
+        mExecutor.advanceClockToLast();
+        mExecutor.runAllReady();
+
+        assertEquals("Callback should not have been invoked.",
+                0, mCallback.mInvocations.size());
+    }
+
+    /**
+     * Tests packages are added when the controller receives a callback from activity manager for
+     * a foreground service start.
+     */
+    @Test
+    public void testGetPackagesWithFgsAddingPackages() throws RemoteException {
+        int numPackages = 20;
+        int numUsers = 3;
+
+        IForegroundServiceObserver observer = prepareObserver();
+
+        assertEquals("List should be empty", 0, mController.getPackagesWithFgs().size());
+
+        List<Pair<Integer, String>> addedPackages = new ArrayList<>();
+        for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) {
+            for (int userId = 0; userId < numUsers; userId++) {
+                String packageName = "package.name." + pkgNumber;
+                addedPackages.add(new Pair(userId, packageName));
+
+                observer.onForegroundStateChanged(new Binder(), packageName, userId, true);
+
+                containsAllAddedPackages(addedPackages, mController.getPackagesWithFgs());
+            }
+        }
+    }
+
+    /**
+     * Tests packages are removed when the controller receives a callback from activity manager for
+     * a foreground service ending.
+     */
+    @Test
+    public void testGetPackagesWithFgsRemovingPackages() throws RemoteException {
+        int numPackages = 20;
+        int numUsers = 3;
+        int arrayLength = numPackages * numUsers;
+
+        String[] packages = new String[arrayLength];
+        int[] users = new int[arrayLength];
+        IBinder[] tokens = new IBinder[arrayLength];
+        for (int pkgNumber = 0; pkgNumber < numPackages; pkgNumber++) {
+            for (int userId = 0; userId < numUsers; userId++) {
+                int i = pkgNumber * numUsers + userId;
+                packages[i] =  "package.name." + pkgNumber;
+                users[i] = userId;
+                tokens[i] = new Binder();
+            }
+        }
+
+        IForegroundServiceObserver observer = prepareObserver();
+
+        for (int i = 0; i < packages.length; i++) {
+            observer.onForegroundStateChanged(tokens[i], packages[i], users[i], true);
+        }
+
+        assertEquals(packages.length, mController.getPackagesWithFgs().size());
+
+        List<Integer> removeOrder = new ArrayList<>();
+        for (int i = 0; i < packages.length; i++) {
+            removeOrder.add(i);
+        }
+        Collections.shuffle(removeOrder, new Random(12345));
+
+        for (int idx : removeOrder) {
+            removePackageAndAssertRemovedFromList(observer, tokens[idx], packages[idx], users[idx]);
+        }
+
+        assertEquals(0, mController.getPackagesWithFgs().size());
+    }
+
+    /**
+     * Tests a call on stopFgs forwards to activity manager.
+     */
+    @Test
+    public void testStopFgs() throws RemoteException {
+        String pkgName = "package.name";
+        mController.stopFgs(0, pkgName);
+        verify(mActivityManager).makeServicesNonForeground(pkgName, 0);
+    }
+
+    /**
+     * Tests a package which starts multiple services is only listed once and is only removed once
+     * all services are stopped.
+     */
+    @Test
+    public void testSinglePackageWithMultipleServices() throws RemoteException {
+        String packageName = "package.name";
+        int userId = 0;
+        IBinder serviceToken1 = new Binder();
+        IBinder serviceToken2 = new Binder();
+
+        IForegroundServiceObserver observer = prepareObserver();
+
+        assertEquals(0, mController.getPackagesWithFgs().size());
+
+        observer.onForegroundStateChanged(serviceToken1, packageName, userId, true);
+        assertSinglePackage(packageName, userId);
+
+        observer.onForegroundStateChanged(serviceToken2, packageName, userId, true);
+        assertSinglePackage(packageName, userId);
+
+        observer.onForegroundStateChanged(serviceToken2, packageName, userId, false);
+        assertSinglePackage(packageName, userId);
+
+        observer.onForegroundStateChanged(serviceToken1, packageName, userId, false);
+        assertEquals(0, mController.getPackagesWithFgs().size());
+    }
+
+    private IForegroundServiceObserver prepareObserver()
+            throws RemoteException {
+        mController.getPackagesWithFgs();
+
+        ArgumentCaptor<IForegroundServiceObserver> argumentCaptor =
+                ArgumentCaptor.forClass(IForegroundServiceObserver.class);
+        verify(mActivityManager).registerForegroundServiceObserver(argumentCaptor.capture());
+
+        return argumentCaptor.getValue();
+    }
+
+    private void verifyInitIsCalled(Consumer<RunningFgsControllerImpl> c) {
+        RunningFgsControllerImpl spiedController = Mockito.spy(
+                ((RunningFgsControllerImpl) mController));
+        c.accept(spiedController);
+        verify(spiedController, atLeastOnce()).init();
+    }
+
+    private void containsAllAddedPackages(List<Pair<Integer, String>> addedPackages,
+            List<UserPackageTime> runningFgsPackages) {
+        for (Pair<Integer, String> userPkg : addedPackages) {
+            assertTrue(userPkg + " was not found in returned list",
+                    runningFgsPackages.stream().anyMatch(
+                            upt -> userPkg.first == upt.getUserId()
+                                    && Objects.equals(upt.getPackageName(), userPkg.second)));
+        }
+        for (UserPackageTime upt : runningFgsPackages) {
+            int userId = upt.getUserId();
+            String packageName = upt.getPackageName();
+            assertTrue("Unknown <user=" + userId + ", package=" + packageName + ">"
+                            + " in returned list",
+                    addedPackages.stream().anyMatch(userPkg -> userPkg.first == userId
+                            && Objects.equals(packageName, userPkg.second)));
+        }
+    }
+
+    private void removePackageAndAssertRemovedFromList(IForegroundServiceObserver observer,
+            IBinder token, String pkg, int userId) throws RemoteException {
+        observer.onForegroundStateChanged(token, pkg, userId, false);
+        List<UserPackageTime> packagesWithFgs = mController.getPackagesWithFgs();
+        assertFalse("Package \"" + pkg + "\" was not removed",
+                packagesWithFgs.stream().anyMatch(upt ->
+                        Objects.equals(upt.getPackageName(), pkg) && upt.getUserId() == userId));
+    }
+
+    private void assertSinglePackage(String packageName, int userId) {
+        assertEquals(1, mController.getPackagesWithFgs().size());
+        assertEquals(packageName, mController.getPackagesWithFgs().get(0).getPackageName());
+        assertEquals(userId, mController.getPackagesWithFgs().get(0).getUserId());
+    }
+
+    private static class TestCallback implements RunningFgsController.Callback {
+
+        private List<List<UserPackageTime>> mInvocations = new ArrayList<>();
+
+        @Override
+        public void onFgsPackagesChanged(List<UserPackageTime> packages) {
+            mInvocations.add(packages);
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
index a39971d..9f152e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
@@ -269,6 +269,21 @@
     }
 
     @Test
+    public void testDisableWiFiWithVcnWithUnderlyingWifi() {
+        String testSsid = "Test VCN SSID";
+        setWifiEnabled(true);
+        verifyLastWifiIcon(false, WifiIcons.WIFI_NO_NETWORK);
+
+        mNetworkController.setNoNetworksAvailable(false);
+        setWifiStateForVcn(true, testSsid);
+        setWifiLevelForVcn(1);
+        verifyLastMobileDataIndicatorsForVcn(true, 1, TelephonyIcons.ICON_CWF, false);
+
+        setWifiEnabled(false);
+        verifyLastMobileDataIndicatorsForVcn(false, 1, 0, false);
+    }
+
+    @Test
     public void testCallStrengh() {
         if (true) return;
         String testSsid = "Test SSID";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index de627de..1961ab2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -214,6 +214,8 @@
 
         // THEN the session is created
         verify(smartspaceManager).createSmartspaceSession(any())
+        // THEN an event notifier is registered
+        verify(plugin).registerSmartspaceEventNotifier(any())
     }
 
     @Test
@@ -241,7 +243,7 @@
     }
 
     @Test
-    fun testEmptyListIsEmittedAfterDisconnect() {
+    fun testEmptyListIsEmittedAndNotifierRemovedAfterDisconnect() {
         // GIVEN a registered listener on an active session
         connectSession()
         clearInvocations(plugin)
@@ -250,8 +252,9 @@
         controller.stateChangeListener.onViewDetachedFromWindow(smartspaceView as View)
         controller.disconnect()
 
-        // THEN the listener receives an empty list of targets
+        // THEN the listener receives an empty list of targets and unregisters the notifier
         verify(plugin).onTargetsAvailable(emptyList())
+        verify(plugin).registerSmartspaceEventNotifier(null)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index f62de51..dc83c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -75,6 +75,7 @@
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
@@ -201,6 +202,7 @@
                 mLeakDetector,
                 mock(ForegroundServiceDismissalFeatureController.class),
                 mock(IStatusBarService.class),
+                NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class)
         );
         mEntryManager.initialize(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index b02a336..ed8b532 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -37,6 +37,7 @@
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
+import androidx.annotation.NonNull;
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
 
@@ -50,6 +51,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -78,6 +80,8 @@
             mock(StatusBarNotification.class);
 
     @Mock
+    DebugModeFilterProvider mDebugModeFilterProvider;
+    @Mock
     StatusBarStateController mStatusBarStateController;
     @Mock
     KeyguardEnvironment mEnvironment;
@@ -132,7 +136,13 @@
                 mDependency,
                 TestableLooper.get(this));
         mRow = testHelper.createRow();
-        mNotificationFilter = new NotificationFilter(
+        mNotificationFilter = newNotificationFilter();
+    }
+
+    @NonNull
+    private NotificationFilter newNotificationFilter() {
+        return new NotificationFilter(
+                mDebugModeFilterProvider,
                 mStatusBarStateController,
                 mEnvironment,
                 mFsc,
@@ -205,12 +215,7 @@
     public void shouldFilterOtherNotificationWhenDisabled() {
         // GIVEN that the media feature is disabled
         when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
-        NotificationFilter filter = new NotificationFilter(
-                mStatusBarStateController,
-                mEnvironment,
-                mFsc,
-                mUserManager,
-                mMediaFeatureFlag);
+        NotificationFilter filter = newNotificationFilter();
         // WHEN the media filter is asked about an entry
         NotificationEntry otherEntry = new NotificationEntryBuilder().build();
         final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
@@ -222,12 +227,7 @@
     public void shouldFilterOtherNotificationWhenEnabled() {
         // GIVEN that the media feature is enabled
         when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
-        NotificationFilter filter = new NotificationFilter(
-                mStatusBarStateController,
-                mEnvironment,
-                mFsc,
-                mUserManager,
-                mMediaFeatureFlag);
+        NotificationFilter filter = newNotificationFilter();
         // WHEN the media filter is asked about an entry
         NotificationEntry otherEntry = new NotificationEntryBuilder().build();
         final boolean shouldFilter = filter.shouldFilterOut(otherEntry);
@@ -239,12 +239,7 @@
     public void shouldFilterMediaNotificationWhenDisabled() {
         // GIVEN that the media feature is disabled
         when(mMediaFeatureFlag.getEnabled()).thenReturn(false);
-        NotificationFilter filter = new NotificationFilter(
-                mStatusBarStateController,
-                mEnvironment,
-                mFsc,
-                mUserManager,
-                mMediaFeatureFlag);
+        NotificationFilter filter = newNotificationFilter();
         // WHEN the media filter is asked about a media entry
         final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
         // THEN it shouldn't be filtered
@@ -255,12 +250,7 @@
     public void shouldFilterMediaNotificationWhenEnabled() {
         // GIVEN that the media feature is enabled
         when(mMediaFeatureFlag.getEnabled()).thenReturn(true);
-        NotificationFilter filter = new NotificationFilter(
-                mStatusBarStateController,
-                mEnvironment,
-                mFsc,
-                mUserManager,
-                mMediaFeatureFlag);
+        NotificationFilter filter = newNotificationFilter();
         // WHEN the media filter is asked about a media entry
         final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry);
         // THEN it should be filtered
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index 2971c05..b45d78d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -18,6 +18,8 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -28,6 +30,7 @@
     private String mKey = "test_group_key";
     private long mCreationTime = 0;
     @Nullable private GroupEntry mParent = GroupEntry.ROOT_ENTRY;
+    private NotifSection mNotifSection;
     private NotificationEntry mSummary = null;
     private List<NotificationEntry> mChildren = new ArrayList<>();
 
@@ -35,6 +38,7 @@
     public GroupEntry build() {
         GroupEntry ge = new GroupEntry(mKey, mCreationTime);
         ge.setParent(mParent);
+        ge.getAttachState().setSection(mNotifSection);
 
         ge.setSummary(mSummary);
         mSummary.setParent(ge);
@@ -61,6 +65,11 @@
         return this;
     }
 
+    public GroupEntryBuilder setSection(@Nullable NotifSection section) {
+        mNotifSection = section;
+        return this;
+    }
+
     public GroupEntryBuilder setSummary(
             NotificationEntry summary) {
         mSummary = summary;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
new file mode 100644
index 0000000..892575a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.notification.collection
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.Observer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+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
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLiveDataImplTest : SysuiTestCase() {
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val liveDataImpl: NotifLiveDataImpl<Int> = NotifLiveDataImpl("tst", 9, executor)
+    private val syncObserver: Observer<Int> = mock()
+    private val asyncObserver: Observer<Int> = mock()
+
+    @Before
+    fun setup() {
+        allowTestableLooperAsMainThread()
+        liveDataImpl.addSyncObserver(syncObserver)
+        liveDataImpl.addAsyncObserver(asyncObserver)
+    }
+
+    @Test
+    fun testGetInitialValue() {
+        assertThat(liveDataImpl.value).isEqualTo(9)
+    }
+
+    @Test
+    fun testGetModifiedValue() {
+        liveDataImpl.value = 13
+        assertThat(liveDataImpl.value).isEqualTo(13)
+    }
+
+    @Test
+    fun testGetsModifiedValueFromWithinSyncObserver() {
+        liveDataImpl.addSyncObserver { intVal ->
+            assertThat(intVal).isEqualTo(13)
+            assertThat(liveDataImpl.value).isEqualTo(13)
+        }
+        liveDataImpl.value = 13
+    }
+
+    @Test
+    fun testDoesNotAlertsRemovedObservers() {
+        liveDataImpl.removeObserver(syncObserver)
+        liveDataImpl.removeObserver(asyncObserver)
+
+        liveDataImpl.value = 13
+
+        // There should be no runnables on the executor
+        assertThat(executor.runAllReady()).isEqualTo(0)
+
+        // And observers should not be called
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testDoesNotAsyncObserversRemovedSinceChange() {
+        liveDataImpl.value = 13
+        liveDataImpl.removeObserver(asyncObserver)
+
+        // There should be a runnable that will get executed...
+        assertThat(executor.runAllReady()).isEqualTo(1)
+
+        // ...but async observers should not be called
+        verifyNoMoreInteractions(asyncObserver)
+    }
+
+    @Test
+    fun testAlertsObservers() {
+        liveDataImpl.value = 13
+
+        // Verify that the synchronous observer is called immediately
+        verify(syncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Verify that the asynchronous observer is called when the executor runs
+        assertThat(executor.runAllReady()).isEqualTo(1)
+        verify(asyncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testAlertsObserversFromDispatcher() {
+        // GIVEN that we use setValueAndProvideDispatcher()
+        val dispatcher = liveDataImpl.setValueAndProvideDispatcher(13)
+
+        // VERIFY that nothing is done before the dispatcher is called
+        assertThat(executor.numPending()).isEqualTo(0)
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // WHEN the dispatcher is invoked...
+        dispatcher.invoke()
+
+        // Verify that the synchronous observer is called immediately
+        verify(syncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Verify that the asynchronous observer is called when the executor runs
+        assertThat(executor.runAllReady()).isEqualTo(1)
+        verify(asyncObserver).onChanged(eq(13))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testSkipsAllObserversIfValueDidNotChange() {
+        liveDataImpl.value = 9
+        // Does not add a runnable
+        assertThat(executor.runAllReady()).isEqualTo(0)
+        // Setting the current value does not call synchronous observers
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+
+    @Test
+    fun testSkipsAsyncObserversWhenValueTogglesBack() {
+        liveDataImpl.value = 13
+        liveDataImpl.value = 11
+        liveDataImpl.value = 9
+
+        // Synchronous observers will receive every change event immediately
+        inOrder(syncObserver).apply {
+            verify(syncObserver).onChanged(eq(13))
+            verify(syncObserver).onChanged(eq(11))
+            verify(syncObserver).onChanged(eq(9))
+        }
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Running the first runnable on the queue will just emit the most recent value
+        assertThat(executor.runNextReady()).isTrue()
+        verify(asyncObserver).onChanged(eq(9))
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+
+        // Running the next 2 runnable will have no effect
+        assertThat(executor.runAllReady()).isEqualTo(2)
+        verifyNoMoreInteractions(syncObserver, asyncObserver)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
new file mode 100644
index 0000000..9c8ac5c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.notification.collection
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+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.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.UnsupportedOperationException
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifLiveDataStoreImplTest : SysuiTestCase() {
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private val liveDataStoreImpl = NotifLiveDataStoreImpl(executor)
+
+    @Before
+    fun setup() {
+        allowTestableLooperAsMainThread()
+    }
+
+    @Test
+    fun testAllObserversSeeConsistentValues() {
+        val entry1 = NotificationEntryBuilder().setId(1).build()
+        val entry2 = NotificationEntryBuilder().setId(2).build()
+        val observer: (Any) -> Unit = {
+            assertThat(liveDataStoreImpl.hasActiveNotifs.value).isEqualTo(true)
+            assertThat(liveDataStoreImpl.activeNotifCount.value).isEqualTo(2)
+            assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2))
+        }
+        liveDataStoreImpl.hasActiveNotifs.addSyncObserver(observer)
+        liveDataStoreImpl.hasActiveNotifs.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addAsyncObserver(observer)
+        liveDataStoreImpl.setActiveNotifList(listOf(entry1, entry2))
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testOriginalListIsCopied() {
+        val entry1 = NotificationEntryBuilder().setId(1).build()
+        val entry2 = NotificationEntryBuilder().setId(2).build()
+        val mutableInputList = mutableListOf(entry1, entry2)
+        val observer: (Any) -> Unit = {
+            mutableInputList.clear()
+            assertThat(liveDataStoreImpl.hasActiveNotifs.value).isEqualTo(true)
+            assertThat(liveDataStoreImpl.activeNotifCount.value).isEqualTo(2)
+            assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2))
+        }
+        liveDataStoreImpl.hasActiveNotifs.addSyncObserver(observer)
+        liveDataStoreImpl.hasActiveNotifs.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifCount.addAsyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addAsyncObserver(observer)
+        liveDataStoreImpl.setActiveNotifList(mutableInputList)
+        executor.runAllReady()
+    }
+
+    @Test
+    fun testProvidedListIsUnmodifiable() {
+        val entry1 = NotificationEntryBuilder().setId(1).build()
+        val entry2 = NotificationEntryBuilder().setId(2).build()
+        val observer: (List<NotificationEntry>) -> Unit = { providedValue ->
+            val provided = providedValue as MutableList<NotificationEntry>
+            Assert.assertThrows(UnsupportedOperationException::class.java) {
+                provided.clear()
+            }
+            val current = liveDataStoreImpl.activeNotifList.value as MutableList<NotificationEntry>
+            Assert.assertThrows(UnsupportedOperationException::class.java) {
+                current.clear()
+            }
+            assertThat(liveDataStoreImpl.activeNotifList.value).isEqualTo(listOf(entry1, entry2))
+        }
+        liveDataStoreImpl.activeNotifList.addSyncObserver(observer)
+        liveDataStoreImpl.activeNotifList.addAsyncObserver(observer)
+        liveDataStoreImpl.setActiveNotifList(mutableListOf(entry1, entry2))
+        executor.runAllReady()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
new file mode 100644
index 0000000..6e81c69
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreMocks.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.notification.collection
+
+import com.android.systemui.util.mockito.mock
+import org.mockito.Mockito.`when` as whenever
+
+/** Creates a mock which returns mocks for the NotifLiveDataImpl fields. */
+fun createNotifLiveDataStoreImplMock(): NotifLiveDataStoreImpl {
+    val dataStoreImpl: NotifLiveDataStoreImpl = mock()
+    whenever(dataStoreImpl.hasActiveNotifs).thenReturn(mock())
+    whenever(dataStoreImpl.activeNotifCount).thenReturn(mock())
+    whenever(dataStoreImpl.activeNotifList).thenReturn(mock())
+    return dataStoreImpl
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
deleted file mode 100644
index 287f50c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineTest.kt
+++ /dev/null
@@ -1,83 +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
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.collection.render.RenderStageManager
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class NotifPipelineTest : SysuiTestCase() {
-
-    @Mock private lateinit var notifCollection: NotifCollection
-    @Mock private lateinit var shadeListBuilder: ShadeListBuilder
-    @Mock private lateinit var renderStageManager: RenderStageManager
-    private lateinit var notifPipeline: NotifPipeline
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        notifPipeline = NotifPipeline(notifCollection, shadeListBuilder, renderStageManager)
-        whenever(shadeListBuilder.shadeList).thenReturn(listOf(
-                NotificationEntryBuilder().setPkg("foo").setId(1).build(),
-                NotificationEntryBuilder().setPkg("foo").setId(2).build(),
-                group(
-                        NotificationEntryBuilder().setPkg("bar").setId(1).build(),
-                        NotificationEntryBuilder().setPkg("bar").setId(2).build(),
-                        NotificationEntryBuilder().setPkg("bar").setId(3).build(),
-                        NotificationEntryBuilder().setPkg("bar").setId(4).build()
-                ),
-                NotificationEntryBuilder().setPkg("baz").setId(1).build()
-        ))
-    }
-
-    private fun group(summary: NotificationEntry, vararg children: NotificationEntry): GroupEntry {
-        return GroupEntry(summary.key, summary.creationTime).also { group ->
-            group.summary = summary
-            for (it in children) {
-                group.addChild(it)
-            }
-        }
-    }
-
-    @Test
-    fun testGetShadeListCount() {
-        assertThat(notifPipeline.getShadeListCount()).isEqualTo(7)
-    }
-
-    @Test
-    fun testGetFlatShadeList() {
-        assertThat(notifPipeline.getFlatShadeList().map { it.key }).containsExactly(
-                "0|foo|1|null|0",
-                "0|foo|2|null|0",
-                "0|bar|1|null|0",
-                "0|bar|2|null|0",
-                "0|bar|3|null|0",
-                "0|bar|4|null|0",
-                "0|baz|1|null|0"
-        ).inOrder()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
new file mode 100644
index 0000000..59fc591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.statusbar.notification.collection.render.NotifStackController
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class DataStoreCoordinatorTest : SysuiTestCase() {
+    private lateinit var coordinator: DataStoreCoordinator
+    private lateinit var afterRenderListListener: OnAfterRenderListListener
+
+    private lateinit var entry: NotificationEntry
+
+    @Mock private lateinit var pipeline: NotifPipeline
+    @Mock private lateinit var notifLiveDataStoreImpl: NotifLiveDataStoreImpl
+    @Mock private lateinit var stackController: NotifStackController
+    @Mock private lateinit var section: NotifSection
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+        entry = NotificationEntryBuilder().setSection(section).build()
+        coordinator = DataStoreCoordinator(notifLiveDataStoreImpl)
+        coordinator.attach(pipeline)
+        afterRenderListListener = withArgCaptor {
+            verify(pipeline).addOnAfterRenderListListener(capture())
+        }
+    }
+
+    @Test
+    fun testUpdateDataStore_withOneEntry() {
+        afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+        verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf(entry)))
+        verifyNoMoreInteractions(notifLiveDataStoreImpl)
+    }
+
+    @Test
+    fun testUpdateDataStore_withGroups() {
+        afterRenderListListener.onAfterRenderList(
+            listOf(
+                notificationEntry("foo", 1),
+                notificationEntry("foo", 2),
+                GroupEntryBuilder().setSummary(
+                    notificationEntry("bar", 1)
+                ).setChildren(
+                    listOf(
+                        notificationEntry("bar", 2),
+                        notificationEntry("bar", 3),
+                        notificationEntry("bar", 4)
+                    )
+                ).setSection(section).build(),
+                notificationEntry("baz", 1)
+            ),
+            stackController
+        )
+        val list: List<NotificationEntry> = withArgCaptor {
+            verify(notifLiveDataStoreImpl).setActiveNotifList(capture())
+        }
+        assertThat(list.map { it.key }).containsExactly(
+            "0|foo|1|null|0",
+            "0|foo|2|null|0",
+            "0|bar|1|null|0",
+            "0|bar|2|null|0",
+            "0|bar|3|null|0",
+            "0|bar|4|null|0",
+            "0|baz|1|null|0"
+        ).inOrder()
+        verifyNoMoreInteractions(notifLiveDataStoreImpl)
+    }
+
+    private fun notificationEntry(pkg: String, id: Int) =
+        NotificationEntryBuilder().setPkg(pkg).setId(id).setSection(section).build()
+
+    @Test
+    fun testUpdateDataStore_withZeroEntries_whenNewPipelineEnabled() {
+        afterRenderListListener.onAfterRenderList(listOf(), stackController)
+        verify(notifLiveDataStoreImpl).setActiveNotifList(eq(listOf()))
+        verifyNoMoreInteractions(notifLiveDataStoreImpl)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
index 395aec3..2dfb9fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
@@ -46,6 +46,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveData;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,6 +67,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 
@@ -82,6 +85,8 @@
 
     // Dependency mocks:
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
+    @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifList;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotifPipeline mNotifPipeline;
@@ -97,6 +102,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifList);
 
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
@@ -112,6 +118,7 @@
                 mListener,
                 mUiBgExecutor,
                 mNotifPipelineFlags,
+                mNotifLiveDataStore,
                 mVisibilityProvider,
                 mEntryManager,
                 mNotifPipeline,
@@ -145,7 +152,7 @@
                 any(NotificationVisibility[].class));
 
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -167,7 +174,7 @@
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -196,7 +203,7 @@
 
     @Test
     public void testLogPanelShownOnWake() {
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -212,7 +219,7 @@
 
     @Test
     public void testLogPanelShownOnShadePull() {
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAwake();
         // Now expand panel
         mLogger.onPanelExpandedChanged(true);
@@ -240,7 +247,7 @@
                 .build();
         entry.setRow(mRow);
 
-        when(mEntryManager.getVisibleNotifications()).thenReturn(Lists.newArrayList(entry));
+        when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(entry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -254,6 +261,7 @@
         TestableNotificationLogger(NotificationListener notificationListener,
                 Executor uiBgExecutor,
                 NotifPipelineFlags notifPipelineFlags,
+                NotifLiveDataStore notifLiveDataStore,
                 NotificationVisibilityProvider visibilityProvider,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -264,6 +272,7 @@
                     notificationListener,
                     uiBgExecutor,
                     notifPipelineFlags,
+                    notifLiveDataStore,
                     visibilityProvider,
                     entryManager,
                     notifPipeline,
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 3a9b297..4d861f9 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
@@ -46,6 +46,8 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveData;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -65,6 +67,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.Executor;
 
@@ -82,6 +85,8 @@
 
     // Dependency mocks:
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
+    @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifEntries;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private NotifPipeline mNotifPipeline;
@@ -98,6 +103,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
+        when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries);
 
         mEntry = new NotificationEntryBuilder()
                 .setPkg(TEST_PACKAGE_NAME)
@@ -113,6 +119,7 @@
                 mListener,
                 mUiBgExecutor,
                 mNotifPipelineFlags,
+                mNotifLiveDataStore,
                 mVisibilityProvider,
                 mEntryManager,
                 mNotifPipeline,
@@ -146,7 +153,7 @@
                 any(NotificationVisibility[].class));
 
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -168,7 +175,7 @@
     public void testStoppingNotificationLoggingReportsCurrentNotifications()
             throws Exception {
         when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
         TestableLooper.get(this).processAllMessages();
         mUiBgExecutor.runAllReady();
@@ -197,7 +204,7 @@
 
     @Test
     public void testLogPanelShownOnWake() {
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -213,7 +220,7 @@
 
     @Test
     public void testLogPanelShownOnShadePull() {
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(mEntry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(mEntry));
         setStateAwake();
         // Now expand panel
         mLogger.onPanelExpandedChanged(true);
@@ -241,7 +248,7 @@
                 .build();
         entry.setRow(mRow);
 
-        when(mNotifPipeline.getFlatShadeList()).thenReturn(Lists.newArrayList(entry));
+        when(mActiveNotifEntries.getValue()).thenReturn(Lists.newArrayList(entry));
         setStateAsleep();
         mLogger.onDozingChanged(false);  // Wake to lockscreen
         assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
@@ -255,6 +262,7 @@
         TestableNotificationLogger(NotificationListener notificationListener,
                 Executor uiBgExecutor,
                 NotifPipelineFlags notifPipelineFlags,
+                NotifLiveDataStore notifLiveDataStore,
                 NotificationVisibilityProvider visibilityProvider,
                 NotificationEntryManager entryManager,
                 NotifPipeline notifPipeline,
@@ -265,6 +273,7 @@
                     notificationListener,
                     uiBgExecutor,
                     notifPipelineFlags,
+                    notifLiveDataStore,
                     visibilityProvider,
                     entryManager,
                     notifPipeline,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index eeda9dd..a890414 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -68,6 +68,7 @@
 import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreMocksKt;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -191,6 +192,7 @@
                 mLeakDetector,
                 mock(ForegroundServiceDismissalFeatureController.class),
                 mock(IStatusBarService.class),
+                NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
                 mock(DumpManager.class)
         );
         mEntryManager.initialize(
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 9be2837..eda0e83 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
@@ -22,6 +22,7 @@
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
 
+import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertEquals;
@@ -102,6 +103,7 @@
     @Mock private NotificationSwipeHelper mNotificationSwipeHelper;
     @Mock private NotificationStackScrollLayoutController mStackScrollLayoutController;
     @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    @Mock private NotificationShelf mNotificationShelf;
 
     @Before
     @UiThreadTest
@@ -123,13 +125,13 @@
         mDependency.injectTestDependency(GroupMembershipManager.class, mGroupMembershipManger);
         mDependency.injectTestDependency(GroupExpansionManager.class, mGroupExpansionManager);
         mDependency.injectTestDependency(AmbientState.class, mAmbientState);
+        mDependency.injectTestDependency(NotificationShelf.class, mNotificationShelf);
         mDependency.injectTestDependency(
                 UnlockedScreenOffAnimationController.class, mUnlockedScreenOffAnimationController);
 
         NotificationShelfController notificationShelfController =
                 mock(NotificationShelfController.class);
-        NotificationShelf notificationShelf = mock(NotificationShelf.class);
-        when(notificationShelfController.getView()).thenReturn(notificationShelf);
+        when(notificationShelfController.getView()).thenReturn(mNotificationShelf);
         when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn(
                 new NotificationSection[]{
                         mNotificationSection
@@ -159,7 +161,7 @@
                         anyBoolean());
         doNothing().when(mGroupExpansionManager).collapseGroups();
         doNothing().when(mExpandHelper).cancelImmediately();
-        doNothing().when(notificationShelf).setAnimationsEnabled(anyBoolean());
+        doNothing().when(mNotificationShelf).setAnimationsEnabled(anyBoolean());
     }
 
     @Test
@@ -202,21 +204,43 @@
 
     @Test
     @UiThreadTest
-    public void testSetExpandedHeight_blockingHelperManagerReceivedCallbacks() {
-        final float[] expectedHeight = {0f};
-        final float[] expectedAppear = {0f};
+    public void testSetExpandedHeight_listenerReceivedCallbacks() {
+        final float expectedHeight = 0f;
 
         mStackScroller.addOnExpandedHeightChangedListener((height, appear) -> {
-            Assert.assertEquals(expectedHeight[0], height, 0);
-            Assert.assertEquals(expectedAppear[0], appear, .1);
+            Assert.assertEquals(expectedHeight, height, 0);
         });
-        expectedHeight[0] = 1f;
-        expectedAppear[0] = 1f;
-        mStackScroller.setExpandedHeight(expectedHeight[0]);
+        mStackScroller.setExpandedHeight(expectedHeight);
+    }
 
-        expectedHeight[0] = 100f;
-        expectedAppear[0] = 0f;
-        mStackScroller.setExpandedHeight(expectedHeight[0]);
+    @Test
+    public void testAppearFractionCalculation() {
+        // appear start position
+        when(mNotificationShelf.getIntrinsicHeight()).thenReturn(100);
+        // because it's the same as shelf height, appear start position equals shelf height
+        mStackScroller.mStatusBarHeight = 100;
+        // appear end position
+        when(mEmptyShadeView.getHeight()).thenReturn(200);
+
+        assertEquals(0f, mStackScroller.calculateAppearFraction(100));
+        assertEquals(1f, mStackScroller.calculateAppearFraction(200));
+        assertEquals(0.5f, mStackScroller.calculateAppearFraction(150));
+    }
+
+    @Test
+    public void testAppearFractionCalculationIsNotNegativeWhenShelfBecomesSmaller() {
+        // this situation might occur if status bar height is defined in pixels while shelf height
+        // in dp and screen density changes - appear start position
+        // (calculated in NSSL#getMinExpansionHeight) that is adjusting for status bar might
+        // increase and become bigger that end position, which should be prevented
+
+        // appear start position
+        when(mNotificationShelf.getIntrinsicHeight()).thenReturn(80);
+        mStackScroller.mStatusBarHeight = 100;
+        // appear end position
+        when(mEmptyShadeView.getHeight()).thenReturn(90);
+
+        assertThat(mStackScroller.calculateAppearFraction(100)).isAtLeast(0);
     }
 
     @Test
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 5b60c9e..ea68143 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
@@ -4,6 +4,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.EmptyShadeView
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -55,4 +56,24 @@
         // top margin presence should decrease heads up translation up to minHeadsUpTranslation
         assertThat(expandableViewState.yTranslation).isEqualTo(minHeadsUpTranslation)
     }
-}
\ No newline at end of file
+
+    @Test
+    fun resetViewStates_childIsEmptyShadeView_viewIsCenteredVertically() {
+        stackScrollAlgorithm.initView(context)
+        val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
+            layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
+        }
+        hostView.removeAllViews()
+        hostView.addView(emptyShadeView)
+        ambientState.layoutMaxHeight = 1280
+
+        stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
+
+        val closeHandleUnderlapHeight =
+            context.resources.getDimensionPixelSize(R.dimen.close_handle_underlap)
+        val fullHeight =
+            ambientState.layoutMaxHeight + closeHandleUnderlapHeight - ambientState.stackY
+        val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
+        assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
+    }
+}
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 07debe6..c3349f1 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
@@ -381,16 +381,15 @@
     }
 
     @Test
-    public void onUdfpsConsecutivelyFailedThreeTimes_showBouncer() {
+    public void onUdfpsConsecutivelyFailedTwoTimes_showBouncer() {
         // GIVEN UDFPS is supported
         when(mUpdateMonitor.isUdfpsSupported()).thenReturn(true);
 
-        // WHEN udfps fails twice - then don't show the bouncer
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+        // WHEN udfps fails once - then don't show the bouncer
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
 
-        // WHEN udfps fails the third time
+        // WHEN udfps fails the second time
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN show the bouncer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 391a64e..e4db072 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -17,9 +17,7 @@
 package com.android.systemui.statusbar.phone;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.reset;
@@ -64,7 +62,7 @@
     @Mock private BatteryController mBatteryController;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private DumpManager mDumpManager;
-    @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
 
     @Before
     public void setup() {
@@ -78,7 +76,7 @@
             mTunerService,
             mDumpManager,
             mFeatureFlags,
-            mUnlockedScreenOffAnimationController
+            mScreenOffAnimationController
         );
     }
     @Test
@@ -128,9 +126,7 @@
         when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
         mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
         when(mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)).thenReturn(true);
-        when(mUnlockedScreenOffAnimationController.shouldPlayUnlockedScreenOffAnimation())
-                .thenReturn(true);
-        assertTrue(mDozeParameters.shouldControlUnlockedScreenOff());
+        when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
 
         // Trigger the setter for the current value.
         mDozeParameters.setControlScreenOffAnimation(mDozeParameters.shouldControlScreenOff());
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 8d05e66..37cf748 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
@@ -278,11 +278,11 @@
     }
 
     @Test
-    public void updateViewState_qsExpansionOne_viewHidden() {
+    public void updateViewState_dragProgressOne_viewHidden() {
         mController.onViewAttached();
         updateStateToKeyguard();
 
-        mNotificationPanelViewStateProvider.setQsExpansionFraction(1f);
+        mNotificationPanelViewStateProvider.setLockscreenShadeDragProgress(1f);
 
         mController.updateViewState();
 
@@ -356,6 +356,7 @@
         private float mPanelViewExpandedHeight = 100f;
         private float mQsExpansionFraction = 0f;
         private boolean mShouldHeadsUpBeVisible = false;
+        private float mLockscreenShadeDragProgress = 0f;
 
         @Override
         public float getPanelViewExpandedHeight() {
@@ -372,6 +373,11 @@
             return mShouldHeadsUpBeVisible;
         }
 
+        @Override
+        public float getLockscreenShadeDragProgress() {
+            return mLockscreenShadeDragProgress;
+        }
+
         public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
             this.mPanelViewExpandedHeight = panelViewExpandedHeight;
         }
@@ -383,5 +389,9 @@
         public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
             this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
         }
+
+        public void setLockscreenShadeDragProgress(float lockscreenShadeDragProgress) {
+            this.mLockscreenShadeDragProgress = lockscreenShadeDragProgress;
+        }
     }
 }
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 74f08ab..9664035 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
@@ -16,14 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,13 +32,13 @@
 import android.view.ViewPropertyAnimator;
 import android.view.WindowManager;
 
+import androidx.lifecycle.Observer;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotifLiveData;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,18 +57,19 @@
     private static final int LIGHTS_ON = 0;
     private static final int LIGHTS_OUT = APPEARANCE_LOW_PROFILE_BARS;
 
-    @Mock private NotificationEntryManager mEntryManager;
+    @Mock private NotifLiveData<Boolean> mHasActiveNotifs;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
     @Mock private CommandQueue mCommandQueue;
     @Mock private WindowManager mWindowManager;
     @Mock private Display mDisplay;
 
-    @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
+    @Captor private ArgumentCaptor<Observer<Boolean>> mObserverCaptor;
     @Captor private ArgumentCaptor<CommandQueue.Callbacks> mCallbacksCaptor;
 
     private View mLightsOutView;
     private LightsOutNotifController mLightsOutNotifController;
     private int mDisplayId;
-    private NotificationEntryListener mEntryListener;
+    private Observer<Boolean> mHaActiveNotifsObserver;
     private CommandQueue.Callbacks mCallbacks;
 
     @Before
@@ -80,14 +79,20 @@
         mLightsOutView = new View(mContext);
         when(mWindowManager.getDefaultDisplay()).thenReturn(mDisplay);
         when(mDisplay.getDisplayId()).thenReturn(mDisplayId);
+        when(mNotifLiveDataStore.getHasActiveNotifs()).thenReturn(mHasActiveNotifs);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
 
-        mLightsOutNotifController = new LightsOutNotifController(mWindowManager, mEntryManager,
+        mLightsOutNotifController = new LightsOutNotifController(
+                mLightsOutView,
+                mWindowManager,
+                mNotifLiveDataStore,
                 mCommandQueue);
-        mLightsOutNotifController.setLightsOutNotifView(mLightsOutView);
+        mLightsOutNotifController.init();
+        mLightsOutNotifController.onViewAttached();
 
         // Capture the entry listener object so we can simulate events in tests below
-        verify(mEntryManager).addNotificationEntryListener(mListenerCaptor.capture());
-        mEntryListener = Objects.requireNonNull(mListenerCaptor.getValue());
+        verify(mHasActiveNotifs).addSyncObserver(mObserverCaptor.capture());
+        mHaActiveNotifsObserver = Objects.requireNonNull(mObserverCaptor.getValue());
 
         // Capture the callback object so we can simulate callback events in tests below
         verify(mCommandQueue).addCallback(mCallbacksCaptor.capture());
@@ -137,7 +142,7 @@
     @Test
     public void testLightsOut_withNotifs_onSystemBarAttributesChanged() {
         // GIVEN active visible notifications
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
 
         // WHEN lights out
         mCallbacks.onSystemBarAttributesChanged(
@@ -157,7 +162,7 @@
     @Test
     public void testLightsOut_withoutNotifs_onSystemBarAttributesChanged() {
         // GIVEN no active visible notifications
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
 
         // WHEN lights out
         mCallbacks.onSystemBarAttributesChanged(
@@ -177,7 +182,7 @@
     @Test
     public void testLightsOn_afterLightsOut_onSystemBarAttributesChanged() {
         // GIVEN active visible notifications
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
 
         // WHEN lights on
         mCallbacks.onSystemBarAttributesChanged(
@@ -197,15 +202,15 @@
     @Test
     public void testEntryAdded() {
         // GIVEN no visible notifications and lights out
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
         mLightsOutNotifController.mAppearance = LIGHTS_OUT;
         mLightsOutNotifController.updateLightsOutView();
         assertIsShowingDot(false);
 
         // WHEN an active notification is added
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
         assertTrue(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onNotificationAdded(mock(NotificationEntry.class));
+        mHaActiveNotifsObserver.onChanged(true);
 
         // THEN we should see the dot view
         assertIsShowingDot(true);
@@ -214,38 +219,20 @@
     @Test
     public void testEntryRemoved() {
         // GIVEN a visible notification and lights out
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
+        when(mHasActiveNotifs.getValue()).thenReturn(true);
         mLightsOutNotifController.mAppearance = LIGHTS_OUT;
         mLightsOutNotifController.updateLightsOutView();
         assertIsShowingDot(true);
 
         // WHEN all active notifications are removed
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
+        when(mHasActiveNotifs.getValue()).thenReturn(false);
         assertFalse(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onEntryRemoved(
-                mock(NotificationEntry.class), null, false, REASON_CANCEL_ALL);
+        mHaActiveNotifsObserver.onChanged(false);
 
         // THEN we shouldn't see the dot view
         assertIsShowingDot(false);
     }
 
-    @Test
-    public void testEntryUpdated() {
-        // GIVEN no visible notifications and lights out
-        when(mEntryManager.hasActiveNotifications()).thenReturn(false);
-        mLightsOutNotifController.mAppearance = LIGHTS_OUT;
-        mLightsOutNotifController.updateLightsOutView();
-        assertIsShowingDot(false);
-
-        // WHEN an active notification is added
-        when(mEntryManager.hasActiveNotifications()).thenReturn(true);
-        assertTrue(mLightsOutNotifController.shouldShowDot());
-        mEntryListener.onPostEntryUpdated(mock(NotificationEntry.class));
-
-        // THEN we should see the dot view
-        assertIsShowingDot(true);
-    }
-
     private void assertIsShowingDot(boolean isShowing) {
         // cancel the animation so we can check the end state
         final ViewPropertyAnimator animation = mLightsOutView.animate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index bd4efdb..1582cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -68,7 +68,7 @@
     @Mock
     StatusBarWindowController mStatusBarWindowController;
     @Mock
-    UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    ScreenOffAnimationController mScreenOffAnimationController;
     private NotificationIconAreaController mController;
     @Mock
     private Bubbles mBubbles;
@@ -91,7 +91,7 @@
                 mDemoModeController,
                 mDarkIconDispatcher,
                 mStatusBarWindowController,
-                mUnlockedScreenOffAnimationController);
+                mScreenOffAnimationController);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 99800e4..1cd9b9e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -99,6 +99,7 @@
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.idle.IdleHostViewController;
@@ -198,7 +199,7 @@
     @Mock
     private DozeParameters mDozeParameters;
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
     private NotificationPanelView mView;
     @Mock
@@ -382,7 +383,7 @@
         mConfiguration.orientation = ORIENTATION_PORTRAIT;
         when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
         mDisplayMetrics.density = 100;
-        when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
+        when(mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHADE_DRAG)).thenReturn(true);
         when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade))
                 .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE);
         when(mResources.getDimensionPixelSize(R.dimen.qs_panel_width)).thenReturn(400);
@@ -424,6 +425,7 @@
                 .thenReturn(mKeyguardUserSwitcherComponent);
         when(mKeyguardUserSwitcherComponent.getKeyguardUserSwitcherController())
                 .thenReturn(mKeyguardUserSwitcherController);
+        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(true);
 
         doAnswer((Answer<Void>) invocation -> {
             mTouchHandler = invocation.getArgument(0);
@@ -437,7 +439,7 @@
                                 mInteractionJankMonitor),
                         mKeyguardBypassController,
                         mDozeParameters,
-                        mUnlockedScreenOffAnimationController);
+                        mScreenOffAnimationController);
         mConfigurationController = new ConfigurationControllerImpl(mContext);
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                 mContext,
@@ -527,7 +529,7 @@
                 mExecutor,
                 mSecureSettings,
                 mSplitShadeHeaderController,
-                mUnlockedScreenOffAnimationController,
+                mScreenOffAnimationController,
                 mLockscreenGestureLogger,
                 new PanelExpansionStateManager(),
                 mNotificationRemoteInputManager,
@@ -879,11 +881,11 @@
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(LARGE);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
         mNotificationPanelViewController.closeQs();
-        verify(mKeyguardStatusViewController).displayClock(SMALL);
+        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
     }
 
     @Test
@@ -893,12 +895,14 @@
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(LARGE);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController, times(2)).displayClock(LARGE);
-        verify(mKeyguardStatusViewController, never()).displayClock(SMALL);
+        verify(mKeyguardStatusViewController, times(2))
+                .displayClock(LARGE, /* animate */ true);
+        verify(mKeyguardStatusViewController, never())
+                .displayClock(SMALL, /* animate */ true);
     }
 
     @Test
@@ -910,7 +914,20 @@
 
         mNotificationPanelViewController.setDozing(true, false, null);
 
-        verify(mKeyguardStatusViewController).displayClock(LARGE);
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+    }
+
+    @Test
+    public void testSwitchesToBigClockInSplitShadeOnAodAnimateDisabled() {
+        when(mScreenOffAnimationController.shouldAnimateClockChange()).thenReturn(false);
+        mStatusBarStateController.setState(KEYGUARD);
+        enableSplitShade(/* enabled= */ true);
+        when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
+        when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+
+        mNotificationPanelViewController.setDozing(true, false, null);
+
+        verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ false);
     }
 
     @Test
@@ -922,13 +939,13 @@
         // one notification + media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController).displayClock(SMALL);
+        verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
 
         // only media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
         triggerPositionClockAndNotifications();
-        verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL);
-        verify(mKeyguardStatusViewController, never()).displayClock(LARGE);
+        verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
+        verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index 90b8a74..a5651f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -73,7 +73,7 @@
     @Mock ColorExtractor.GradientColors mGradientColors;
     @Mock private DumpManager mDumpManager;
     @Mock private KeyguardStateController mKeyguardStateController;
-    @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private AuthController mAuthController;
     @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters;
 
@@ -89,7 +89,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager, mKeyguardStateController,
-                mUnlockedScreenOffAnimationController, mAuthController);
+                mScreenOffAnimationController, mAuthController);
         mNotificationShadeWindowController.setScrimsVisibilityListener((visibility) -> {});
         mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
 
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 7d266e9..235de1e 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
@@ -26,6 +26,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+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
@@ -57,6 +58,8 @@
     private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent
     @Mock
     private lateinit var progressProvider: ScopedUnfoldTransitionProgressProvider
+    @Mock
+    private lateinit var configurationController: ConfigurationController
 
     private lateinit var view: PhoneStatusBarView
     private lateinit var controller: PhoneStatusBarViewController
@@ -116,7 +119,8 @@
     private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController {
         return PhoneStatusBarViewController.Factory(
             Optional.of(sysuiUnfoldComponent),
-            Optional.of(progressProvider)
+            Optional.of(progressProvider),
+            configurationController
         ).create(view, touchEventHandler)
     }
 
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 42f2206..f21fca2 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
@@ -112,7 +112,7 @@
     @Mock
     private ConfigurationController mConfigurationController;
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private ScreenOffAnimationController mScreenOffAnimationController;
     // 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
@@ -229,7 +229,7 @@
                 mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
                 new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
                 mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
-                mUnlockedScreenOffAnimationController,
+                mScreenOffAnimationController,
                 mPanelExpansionStateManager);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
index 2d548e9..2b1826e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SplitShadeHeaderControllerTest.kt
@@ -1,13 +1,14 @@
 package com.android.systemui.statusbar.phone
 
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.view.View
+import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.qs.HeaderPrivacyIconsController
@@ -37,11 +38,13 @@
     @Mock private lateinit var batteryMeterView: BatteryMeterView
     @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
     @Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
+    @Mock private lateinit var dumpManager: DumpManager
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
     var viewVisibility = View.GONE
 
     private lateinit var splitShadeHeaderController: SplitShadeHeaderController
+    private lateinit var carrierIconSlots: List<String>
 
     @Before
     fun setup() {
@@ -65,14 +68,16 @@
                 privacyIconsController,
                 qsCarrierGroupControllerBuilder,
                 featureFlags,
-                batteryMeterViewController
+                batteryMeterViewController,
+                dumpManager
         )
+        carrierIconSlots = listOf(
+                context.getString(com.android.internal.R.string.status_bar_mobile))
     }
 
     @Test
     fun setVisible_onlyInSplitShade() {
-        splitShadeHeaderController.splitShadeMode = true
-        splitShadeHeaderController.shadeExpanded = true
+        makeShadeVisible()
         assertThat(viewVisibility).isEqualTo(View.VISIBLE)
 
         splitShadeHeaderController.splitShadeMode = false
@@ -81,17 +86,38 @@
 
     @Test
     fun updateListeners_registersWhenVisible() {
-        splitShadeHeaderController.splitShadeMode = true
-        splitShadeHeaderController.shadeExpanded = true
+        makeShadeVisible()
         verify(qsCarrierGroupController).setListening(true)
         verify(statusBarIconController).addIconGroup(any())
     }
 
     @Test
     fun shadeExpandedFraction_updatesAlpha() {
-        splitShadeHeaderController.splitShadeMode = true
-        splitShadeHeaderController.shadeExpanded = true
+        makeShadeVisible()
         splitShadeHeaderController.shadeExpandedFraction = 0.5f
         verify(view).setAlpha(ShadeInterpolation.getContentAlpha(0.5f))
     }
-}
\ No newline at end of file
+
+    @Test
+    fun singleCarrier_enablesCarrierIconsInStatusIcons() {
+        whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
+
+        makeShadeVisible()
+
+        verify(statusIcons).removeIgnoredSlots(carrierIconSlots)
+    }
+
+    @Test
+    fun dualCarrier_disablesCarrierIconsInStatusIcons() {
+        whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
+
+        makeShadeVisible()
+
+        verify(statusIcons).addIgnoredSlots(carrierIconSlots)
+    }
+
+    private fun makeShadeVisible() {
+        splitShadeHeaderController.splitShadeMode = true
+        splitShadeHeaderController.shadeExpanded = true
+    }
+}
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 c5bdfed..5d80bca 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
@@ -43,9 +43,6 @@
 import com.android.keyguard.ViewMediatorCallback;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dock.DockManager;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.DismissCallbackRegistry;
-import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import com.android.systemui.statusbar.NotificationMediaManager;
@@ -79,9 +76,7 @@
     @Mock
     private NotificationPanelViewController mNotificationPanelView;
     @Mock
-    private BiometricUnlockController mBiometrucUnlockController;
-    @Mock
-    private DismissCallbackRegistry mDismissCallbackRegistry;
+    private BiometricUnlockController mBiometricUnlockController;
     @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
     @Mock
@@ -97,15 +92,12 @@
     @Mock
     private KeyguardBouncer mBouncer;
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    @Mock
     private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor;
     @Mock
     private KeyguardMessageArea mKeyguardMessageArea;
     @Mock
     private ShadeController mShadeController;
 
-    private WakefulnessLifecycle mWakefulnessLifecycle;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     @Before
@@ -117,10 +109,6 @@
                 .thenReturn(mBouncer);
         when(mStatusBar.getBouncerContainer()).thenReturn(mContainer);
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
-        mWakefulnessLifecycle = new WakefulnessLifecycle(
-                getContext(),
-                null,
-                mock(DumpManager.class));
         mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(
                 getContext(),
                 mViewMediatorCallback,
@@ -134,15 +122,13 @@
                 mKeyguardStateController,
                 mock(NotificationMediaManager.class),
                 mKeyguardBouncerFactory,
-                mWakefulnessLifecycle,
-                mUnlockedScreenOffAnimationController,
                 mKeyguardMessageAreaFactory,
                 () -> mShadeController);
         mStatusBarKeyguardViewManager.registerStatusBar(
                 mStatusBar,
                 mNotificationPanelView,
                 new PanelExpansionStateManager(),
-                mBiometrucUnlockController,
+                mBiometricUnlockController,
                 mNotificationContainer,
                 mBypassController);
         mStatusBarKeyguardViewManager.show(null);
@@ -261,7 +247,7 @@
 
     @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
-        when(mBiometrucUnlockController.getMode())
+        when(mBiometricUnlockController.getMode())
                 .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
                 /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
@@ -389,6 +375,39 @@
     }
 
     @Test
+    public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
+        // GIVEN alt auth exists, unlocking with biometric isn't allowed
+        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+        when(mBouncer.isShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false);
+
+        // WHEN showGenericBouncer is called
+        final boolean scrimmed = true;
+        mStatusBarKeyguardViewManager.showGenericBouncer(scrimmed);
+
+        // THEN regular bouncer is shown
+        verify(mBouncer).show(anyBoolean(), eq(scrimmed));
+        verify(mAlternateAuthInterceptor, never()).showAlternateAuthBouncer();
+    }
+
+    @Test
+    public void testShowAltAuth_unlockingWithBiometricAllowed() {
+        // GIVEN alt auth exists, unlocking with biometric is allowed
+        mStatusBarKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+        when(mBouncer.isShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(true);
+
+        // WHEN showGenericBouncer is called
+        mStatusBarKeyguardViewManager.showGenericBouncer(true);
+
+        // THEN alt auth bouncer is shown
+        verify(mAlternateAuthInterceptor).showAlternateAuthBouncer();
+        verify(mBouncer, never()).show(anyBoolean(), anyBoolean());
+    }
+
+    @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index a34d2f5..9d5b17e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -114,12 +114,12 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.connectivity.NetworkController;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationFilter;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -136,7 +136,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -148,7 +147,6 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.MessageRouterImpl;
@@ -246,12 +244,10 @@
     @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
     @Mock private VolumeComponent mVolumeComponent;
     @Mock private CommandQueue mCommandQueue;
-    @Mock private CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger;
     @Mock private StatusBarComponent.Factory mStatusBarComponentFactory;
     @Mock private StatusBarComponent mStatusBarComponent;
     @Mock private PluginManager mPluginManager;
     @Mock private LegacySplitScreen mLegacySplitScreen;
-    @Mock private LightsOutNotifController mLightsOutNotifController;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
     @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
@@ -267,22 +263,19 @@
     @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory;
     @Mock private WallpaperController mWallpaperController;
     @Mock private OngoingCallController mOngoingCallController;
-    @Mock private SystemStatusAnimationScheduler mAnimationScheduler;
-    @Mock private StatusBarLocationPublisher mLocationPublisher;
-    @Mock private StatusBarIconController mIconController;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private IWallpaperManager mIWallpaperManager;
     @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    @Mock private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    @Mock private TunerService mTunerService;
+    @Mock private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock private StartingSurface mStartingSurface;
     @Mock private OperatorNameViewController mOperatorNameViewController;
     @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
     @Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock private NotifPipelineFlags mNotifPipelineFlags;
+    @Mock private NotifLiveDataStore mNotifLiveDataStore;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -315,6 +308,7 @@
                 mNotificationListener,
                 mUiBgExecutor,
                 mNotifPipelineFlags,
+                mNotifLiveDataStore,
                 mVisibilityProvider,
                 mock(NotificationEntryManager.class),
                 mock(NotifPipeline.class),
@@ -432,11 +426,9 @@
                 mDozeScrimController,
                 mVolumeComponent,
                 mCommandQueue,
-                mCollapsedStatusBarFragmentLogger,
                 mStatusBarComponentFactory,
                 mPluginManager,
                 Optional.of(mLegacySplitScreen),
-                mLightsOutNotifController,
                 mStatusBarNotificationActivityStarterBuilder,
                 mShadeController,
                 mStatusBarKeyguardViewManager,
@@ -447,7 +439,6 @@
                 mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
-                mOperatorNameViewControllerFactory,
                 mPhoneStatusBarPolicy,
                 mKeyguardIndicationController,
                 mDemoModeController,
@@ -455,11 +446,9 @@
                 mStatusBarTouchableRegionManager,
                 mNotificationIconAreaController,
                 mBrightnessSliderFactory,
+                mScreenOffAnimationController,
                 mWallpaperController,
                 mOngoingCallController,
-                mAnimationScheduler,
-                mLocationPublisher,
-                mIconController,
                 new StatusBarHideIconsForBouncerManager(mCommandQueue, mMainExecutor, mDumpManager),
                 mLockscreenTransitionController,
                 mFeatureFlags,
@@ -468,10 +457,7 @@
                 mMainExecutor,
                 new MessageRouterImpl(mMainExecutor),
                 mWallpaperManager,
-                mUnlockedScreenOffAnimationController,
                 Optional.of(mStartingSurface),
-                mTunerService,
-                mDumpManager,
                 mActivityLaunchAnimator,
                 mNotifPipelineFlags);
         when(mKeyguardViewMediator.registerStatusBar(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
deleted file mode 100644
index b359b9c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.java
+++ /dev/null
@@ -1,95 +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.statusbar.policy;
-
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.assertFalse;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import android.content.res.Configuration;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-
-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 class RemoteInputQuickSettingsDisablerTest extends SysuiTestCase {
-
-    @Mock
-    private CommandQueue mCommandQueue;
-    private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mRemoteInputQuickSettingsDisabler = new RemoteInputQuickSettingsDisabler(mContext,
-                mock(ConfigurationController.class), mCommandQueue);
-    }
-
-    @Test
-    public void shouldEnableQuickSetting_afterDeactiviate() {
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
-        assertFalse(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void shouldDisableQuickSetting_afteActiviate() {
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.FALSE);
-        mRemoteInputQuickSettingsDisabler.setRemoteInputActive(Boolean.TRUE);
-        assertTrue(mRemoteInputQuickSettingsDisabler.mRemoteInputActive);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeToLandscape() {
-        Configuration c = new Configuration(mContext.getResources().getConfiguration());
-        c.orientation = Configuration.ORIENTATION_PORTRAIT;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        c.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        assertTrue(mRemoteInputQuickSettingsDisabler.misLandscape);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-    @Test
-    public void testChangeToPortrait() {
-        Configuration c = new Configuration(mContext.getResources().getConfiguration());
-        c.orientation = Configuration.ORIENTATION_LANDSCAPE;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        c.orientation = Configuration.ORIENTATION_PORTRAIT;
-        mRemoteInputQuickSettingsDisabler.onConfigChanged(c);
-        assertFalse(mRemoteInputQuickSettingsDisabler.misLandscape);
-        verify(mCommandQueue, atLeastOnce()).recomputeDisableFlags(anyInt(), anyBoolean());
-    }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
new file mode 100644
index 0000000..1ab0582
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.statusbar.policy
+
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class RemoteInputQuickSettingsDisablerTest : SysuiTestCase() {
+
+    @Mock lateinit var commandQueue: CommandQueue
+    private lateinit var remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler
+    private lateinit var configuration: Configuration
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        remoteInputQuickSettingsDisabler = RemoteInputQuickSettingsDisabler(
+            mContext,
+            commandQueue, Mockito.mock(ConfigurationController::class.java)
+        )
+        configuration = Configuration(mContext.resources.configuration)
+
+        // Default these conditions to what they need to be to disable QS.
+        mContext.orCreateTestableResources
+            .addOverride(R.bool.config_use_split_notification_shade, /* value= */false)
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(true)
+        configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+    }
+
+    @Test
+    fun whenRemoteInputActiveAndLandscapeAndNotSplitShade_shouldDisableQs() {
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isTrue()
+    }
+
+    @Test
+    fun whenRemoteInputNotActive_shouldNotDisableQs() {
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenSplitShadeEnabled_shouldNotDisableQs() {
+        mContext.orCreateTestableResources
+            .addOverride(R.bool.config_use_split_notification_shade, /* value= */true)
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenPortrait_shouldNotDisableQs() {
+        configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        assertThat(
+            shouldDisableQs(
+                remoteInputQuickSettingsDisabler.adjustDisableFlags(0)
+            )
+        )
+            .isFalse()
+    }
+
+    @Test
+    fun whenRemoteInputChanges_recomputeTriggered() {
+        remoteInputQuickSettingsDisabler.setRemoteInputActive(false)
+
+        verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+            anyInt(), anyBoolean()
+        )
+    }
+
+    @Test
+    fun whenConfigChanges_recomputeTriggered() {
+        configuration.orientation = Configuration.ORIENTATION_PORTRAIT
+        remoteInputQuickSettingsDisabler.onConfigChanged(configuration)
+
+        verify(commandQueue, atLeastOnce()).recomputeDisableFlags(
+            anyInt(), anyBoolean()
+        )
+    }
+
+    private fun shouldDisableQs(state: Int): Boolean {
+        return state and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index ccb4f67..34407b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -77,6 +77,9 @@
 @RunWith(AndroidTestingRunner.class)
 public class ThemeOverlayControllerTest extends SysuiTestCase {
 
+    private static final int USER_SYSTEM = UserHandle.USER_SYSTEM;
+    private static final int USER_SECONDARY = 10;
+
     private ThemeOverlayController mThemeOverlayController;
     @Mock
     private Executor mBgExecutor;
@@ -112,6 +115,9 @@
     private ArgumentCaptor<DeviceProvisionedListener> mDeviceProvisionedListener;
     @Captor
     private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessLifecycleObserver;
+    @Captor
+    private ArgumentCaptor<UserTracker.Callback> mUserTrackerCallback;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -134,6 +140,7 @@
 
         mWakefulnessLifecycle.dispatchFinishedWakingUp();
         mThemeOverlayController.start();
+        verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
         verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
                 eq(UserHandle.USER_ALL));
         verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
@@ -157,7 +164,8 @@
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
                 ArgumentCaptor.forClass(Map.class);
 
@@ -171,12 +179,13 @@
                 .isEqualTo(new OverlayIdentifier("ffff0000"));
 
         // Should not ask again if changed to same value
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         verifyNoMoreInteractions(mThemeOverlayApplier);
 
         // Should not ask again even for new colors until we change wallpapers
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
-                null, null), WallpaperManager.FLAG_SYSTEM);
+                null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verifyNoMoreInteractions(mThemeOverlayApplier);
 
         // But should change theme after changing wallpapers
@@ -185,7 +194,7 @@
         intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true);
         mBroadcastReceiver.getValue().onReceive(null, intent);
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
-                null, null), WallpaperManager.FLAG_SYSTEM);
+                null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
 
@@ -194,7 +203,8 @@
         // Should ask for a new theme when wallpaper colors change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
                 ArgumentCaptor.forClass(Map.class);
 
@@ -212,7 +222,7 @@
         clearInvocations(mThemeOverlayApplier);
         mBroadcastReceiver.getValue().onReceive(null, new Intent(Intent.ACTION_WALLPAPER_CHANGED));
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.BLACK),
-                null, null), WallpaperManager.FLAG_SYSTEM);
+                null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
                 .applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
@@ -230,7 +240,8 @@
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
                 ArgumentCaptor.forClass(Map.class);
 
@@ -258,7 +269,8 @@
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings).putString(
@@ -290,10 +302,13 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(20);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM)).thenReturn(21);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(20);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(21);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings).putString(
@@ -321,10 +336,11 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(-1);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(-1);
 
         mColorsListener.getValue().onColorsChanged(mainColors,
-                WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
+                WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK, USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings).putString(
@@ -350,9 +366,11 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(1);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(1);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+                USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings).putString(
@@ -378,9 +396,11 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(-1);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(-1);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings).putString(
@@ -408,11 +428,14 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(1);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(1);
         // SYSTEM wallpaper is the last applied one
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM)).thenReturn(2);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(2);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings).putString(
@@ -438,11 +461,14 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(1);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(1);
         // SYSTEM wallpaper is the last applied one
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM)).thenReturn(2);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(2);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
         verify(mSecureSettings, never()).putString(
@@ -468,11 +494,14 @@
         when(mSecureSettings.getStringForUser(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_LOCK)).thenReturn(1);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(1);
         // SYSTEM wallpaper is the last applied one
-        when(mWallpaperManager.getWallpaperId(WallpaperManager.FLAG_SYSTEM)).thenReturn(2);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(2);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+                USER_SYSTEM);
 
         verify(mSecureSettings, never()).putString(
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), any());
@@ -483,6 +512,34 @@
     }
 
     @Test
+    public void onUserSwitching_setsTheme() {
+        // Setup users with different colors
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED), null, null);
+        WallpaperColors secondaryColors =
+                new WallpaperColors(Color.valueOf(Color.BLUE), null, null);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(secondaryColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SECONDARY);
+
+        // When changing users
+        clearInvocations(mThemeOverlayApplier);
+        when(mUserTracker.getUserId()).thenReturn(USER_SECONDARY);
+        mUserTrackerCallback.getValue().onUserChanged(USER_SECONDARY, mContext);
+
+        ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
+                ArgumentCaptor.forClass(Map.class);
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(themeOverlays.capture(), any(), anyInt(), any());
+
+        // Assert that we received secondary user colors
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_SYSTEM_PALETTE))
+                .isEqualTo(new OverlayIdentifier("ff0000ff"));
+        assertThat(themeOverlays.getValue().get(OVERLAY_CATEGORY_ACCENT_COLOR))
+                .isEqualTo(new OverlayIdentifier("ff0000ff"));
+    }
+
+    @Test
     public void onProfileAdded_setsTheme() {
         mBroadcastReceiver.getValue().onReceive(null,
                 new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
@@ -516,7 +573,8 @@
         reset(mDeviceProvisionedController);
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
 
@@ -526,11 +584,11 @@
         Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
         intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, true);
         mBroadcastReceiver.getValue().onReceive(null, intent);
-        mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(null, WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
                 any());
         mColorsListener.getValue().onColorsChanged(new WallpaperColors(Color.valueOf(Color.GREEN),
-                null, null), WallpaperManager.FLAG_SYSTEM);
+                null, null), WallpaperManager.FLAG_SYSTEM, USER_SYSTEM);
         verify(mThemeOverlayApplier, never()).applyCurrentUserOverlays(any(), any(), anyInt(),
                 any());
     }
@@ -608,7 +666,8 @@
 
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         // Defers event because we already have initial colors.
         verify(mThemeOverlayApplier, never())
@@ -629,14 +688,16 @@
         // Second color application is not applied.
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         clearInvocations(mThemeOverlayApplier);
 
         // Device went to sleep and second set of colors was applied.
         mainColors =  new WallpaperColors(Color.valueOf(Color.BLUE),
                 Color.valueOf(Color.RED), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
                 .applyCurrentUserOverlays(any(), any(), anyInt(), any());
 
@@ -653,14 +714,16 @@
         // Second color application is not applied.
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
                 Color.valueOf(Color.BLUE), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
 
         clearInvocations(mThemeOverlayApplier);
 
         // Device went to sleep and second set of colors was applied.
         mainColors =  new WallpaperColors(Color.valueOf(Color.BLUE),
                 Color.valueOf(Color.RED), null);
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         verify(mThemeOverlayApplier, never())
                 .applyCurrentUserOverlays(any(), any(), anyInt(), any());
 
@@ -679,7 +742,8 @@
                 eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
                 .thenReturn(jsonString);
 
-        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM);
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
         ArgumentCaptor<Map<String, OverlayIdentifier>> themeOverlays =
                 ArgumentCaptor.forClass(Map.class);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
new file mode 100644
index 0000000..f600b12
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.unfold
+
+import android.hardware.devicestate.DeviceStateManager
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ScreenLifecycle
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
+import com.android.systemui.util.mockito.any
+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.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UnfoldLatencyTrackerTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var latencyTracker: LatencyTracker
+
+    @Mock
+    lateinit var deviceStateManager: DeviceStateManager
+
+    @Mock
+    lateinit var screenLifecycle: ScreenLifecycle
+
+    @Captor
+    private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+    @Captor
+    private lateinit var screenLifecycleCaptor: ArgumentCaptor<ScreenLifecycle.Observer>
+
+    private lateinit var deviceStates: FoldableDeviceStates
+
+    private lateinit var unfoldLatencyTracker: UnfoldLatencyTracker
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        unfoldLatencyTracker = UnfoldLatencyTracker(
+            latencyTracker,
+            deviceStateManager,
+            context.mainExecutor,
+            context,
+            screenLifecycle
+        )
+        deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+        verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
+        verify(screenLifecycle).addObserver(screenLifecycleCaptor.capture())
+    }
+
+    @Test
+    fun unfold_eventPropagated() {
+        sendFoldEvent(folded = false)
+        sendScreenTurnedOnEvent()
+
+        verify(latencyTracker).onActionStart(any())
+        verify(latencyTracker).onActionEnd(any())
+    }
+
+    @Test
+    fun fold_eventNotPropagated() {
+        sendFoldEvent(folded = true)
+        sendScreenTurnedOnEvent() // outer display on.
+
+        verifyNoMoreInteractions(latencyTracker)
+    }
+
+    @Test
+    fun onScreenTurnedOn_stateNeverSet_eventNotPropagated() {
+        sendScreenTurnedOnEvent()
+
+        verifyNoMoreInteractions(latencyTracker)
+    }
+
+    private fun sendFoldEvent(folded: Boolean) {
+        val state = if (folded) deviceStates.folded else deviceStates.unfolded
+        foldStateListenerCaptor.value.onStateChanged(state)
+    }
+
+    private fun sendScreenTurnedOnEvent() {
+        screenLifecycleCaptor.value.onScreenTurnedOn()
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index a1d9a7b..46e0b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -18,22 +18,28 @@
 
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
 import android.testing.AndroidTestingRunner
+import androidx.core.util.Consumer
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
 import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.unfold.util.FoldableDeviceStates
+import com.android.systemui.unfold.util.FoldableTestUtils
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeTrue
 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.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import java.lang.Exception
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
@@ -48,34 +54,40 @@
     @Mock
     private lateinit var deviceStateManager: DeviceStateManager
 
-    private lateinit var foldStateProvider: FoldStateProvider
+    @Mock
+    private lateinit var handler: Handler
+
+    @Captor
+    private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+    @Captor
+    private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
+
+    @Captor
+    private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
+
+    private lateinit var foldStateProvider: DeviceFoldStateProvider
 
     private val foldUpdates: MutableList<Int> = arrayListOf()
     private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
 
-    private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
-    private var foldedDeviceState: Int = 0
-    private var unfoldedDeviceState: Int = 0
+    private lateinit var deviceStates: FoldableDeviceStates
 
-    private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
+    private var scheduledRunnable: Runnable? = null
+    private var scheduledRunnableDelay: Long? = null
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        val foldedDeviceStates: IntArray = context.resources.getIntArray(
-            com.android.internal.R.array.config_foldedDeviceStates)
-        assumeTrue("Test should be launched on a foldable device",
-            foldedDeviceStates.isNotEmpty())
-
-        foldedDeviceState = foldedDeviceStates.maxOrNull()!!
-        unfoldedDeviceState = foldedDeviceState + 1
+        deviceStates = FoldableTestUtils.findDeviceStates(context)
 
         foldStateProvider = DeviceFoldStateProvider(
             context,
             hingeAngleProvider,
             screenStatusProvider,
             deviceStateManager,
-            context.mainExecutor
+            context.mainExecutor,
+            handler
         )
 
         foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
@@ -91,6 +103,22 @@
 
         verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
         verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
+        verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
+
+        whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
+            scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
+            scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
+            null
+        }
+
+        whenever(handler.removeCallbacks(any<Runnable>())).then { invocationOnMock ->
+            val removedRunnable = invocationOnMock.getArgument<Runnable>(0)
+            if (removedRunnable == scheduledRunnable) {
+                scheduledRunnableDelay = null
+                scheduledRunnable = null
+            }
+            null
+        }
     }
 
     @Test
@@ -167,12 +195,96 @@
         assertThat(foldUpdates).isEmpty()
     }
 
+    @Test
+    fun startClosingEvent_afterTimeout_abortEmitted() {
+        sendHingeAngleEvent(90)
+        sendHingeAngleEvent(80)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+    }
+
+    @Test
+    fun startClosingEvent_beforeTimeout_abortNotEmitted() {
+        sendHingeAngleEvent(90)
+        sendHingeAngleEvent(80)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
+        sendHingeAngleEvent(180)
+        sendHingeAngleEvent(90)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+        sendHingeAngleEvent(80)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+        sendHingeAngleEvent(180)
+        sendHingeAngleEvent(90)
+
+        simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here.
+        sendHingeAngleEvent(80)
+        simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here.
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+    }
+
+    @Test
+    fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
+        sendHingeAngleEvent(180)
+
+        sendHingeAngleEvent(90)
+        sendHingeAngleEvent(80)
+
+        assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+    }
+
+    @Test
+    fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
+        val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
+        for (i in 1..maxAngle) {
+            foldUpdates.clear()
+
+            simulateFolding(startAngle = i)
+
+            assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+            simulateTimeout() // Timeout to set the state to aborted.
+        }
+    }
+
+    private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) {
+        val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
+        if (waitTime >= runnableDelay) {
+            scheduledRunnable?.run()
+            scheduledRunnable = null
+            scheduledRunnableDelay = null
+        }
+    }
+
+    private fun simulateFolding(startAngle: Int) {
+        sendHingeAngleEvent(startAngle)
+        sendHingeAngleEvent(startAngle - 1)
+    }
+
     private fun setFoldState(folded: Boolean) {
-        val state = if (folded) foldedDeviceState else unfoldedDeviceState
+        val state = if (folded) deviceStates.folded else deviceStates.unfolded
         foldStateListenerCaptor.value.onStateChanged(state)
     }
 
     private fun fireScreenOnEvent() {
         screenOnListenerCaptor.value.onScreenTurnedOn()
     }
+
+    private fun sendHingeAngleEvent(angle: Int) {
+        hingeAngleCaptor.value.accept(angle.toFloat())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
new file mode 100644
index 0000000..01b5359
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.unfold.util
+
+import android.content.Context
+import org.junit.Assume.assumeTrue
+
+object FoldableTestUtils {
+
+    /** Finds device state for folded and unfolded. */
+    fun findDeviceStates(context: Context): FoldableDeviceStates {
+        val foldedDeviceStates: IntArray = context.resources.getIntArray(
+            com.android.internal.R.array.config_foldedDeviceStates)
+        assumeTrue("Test should be launched on a foldable device",
+            foldedDeviceStates.isNotEmpty())
+
+        val folded = foldedDeviceStates.maxOrNull()!!
+        val unfolded = folded + 1
+        return FoldableDeviceStates(folded = folded, unfolded = unfolded)
+    }
+}
+
+data class FoldableDeviceStates(val folded: Int, val unfolded: Int)
\ No newline at end of file
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
new file mode 100644
index 0000000..db7a8516
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.unfold.util
+
+import android.animation.ValueAnimator
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.util.mockito.any
+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.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
+
+    @Mock
+    lateinit var contentResolver: ContentResolver
+
+    @Mock
+    lateinit var sourceProvider: UnfoldTransitionProgressProvider
+
+    @Mock
+    lateinit var sinkProvider: TransitionProgressListener
+
+    lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+
+    private val sourceProviderListenerCaptor =
+            ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+
+    private val animatorDurationScaleListenerCaptor =
+            ArgumentCaptor.forClass(ContentObserver::class.java)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        progressProvider = ScaleAwareTransitionProgressProvider(
+                sourceProvider,
+                contentResolver
+        )
+
+        verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
+        verify(contentResolver).registerContentObserver(any(), any(),
+                animatorDurationScaleListenerCaptor.capture())
+
+        progressProvider.addCallback(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionStarted_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionStarted()
+
+        verify(sinkProvider).onTransitionStarted()
+    }
+
+    @Test
+    fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionStarted()
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionEnd_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionFinished()
+
+        verify(sinkProvider).onTransitionFinished()
+    }
+
+    @Test
+    fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionFinished()
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    @Test
+    fun onTransitionProgress_animationsEnabled_eventReceived() {
+        setAnimationsEnabled(true)
+
+        source.onTransitionProgress(42f)
+
+        verify(sinkProvider).onTransitionProgress(42f)
+    }
+
+    @Test
+    fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
+        setAnimationsEnabled(false)
+
+        source.onTransitionProgress(42f)
+
+        verifyNoMoreInteractions(sinkProvider)
+    }
+
+    private fun setAnimationsEnabled(enabled: Boolean) {
+        val durationScale = if (enabled) {
+            1f
+        } else {
+            0f
+        }
+        ValueAnimator.setDurationScale(durationScale)
+        animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
+    }
+
+    private val source: TransitionProgressListener
+        get() = sourceProviderListenerCaptor.value
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
new file mode 100644
index 0000000..d645449
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.condition;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.clearInvocations;
+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;
+
+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.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ConditionMonitorTest extends SysuiTestCase {
+    private FakeCondition mCondition1;
+    private FakeCondition mCondition2;
+    private FakeCondition mCondition3;
+    private HashSet<Condition> mConditions;
+
+    private Monitor mConditionMonitor;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mCondition1 = spy(new FakeCondition());
+        mCondition2 = spy(new FakeCondition());
+        mCondition3 = spy(new FakeCondition());
+        mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
+
+        mConditionMonitor = new Monitor(mConditions, null /*callbacks*/);
+    }
+
+    @Test
+    public void addCallback_addFirstCallback_addCallbackToAllConditions() {
+        final Monitor.Callback callback1 =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback1);
+        mConditions.forEach(condition -> verify(condition).addCallback(any()));
+
+        final Monitor.Callback callback2 =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback2);
+        mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
+    }
+
+    @Test
+    public void addCallback_addFirstCallback_reportWithDefaultValue() {
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback);
+        verify(callback).onConditionsChanged(false);
+    }
+
+    @Test
+    public void addCallback_addSecondCallback_reportWithExistingValue() {
+        final Monitor.Callback callback1 =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback1);
+
+        mConditionMonitor.overrideAllConditionsMet(true);
+
+        final Monitor.Callback callback2 =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback2);
+        verify(callback2).onConditionsChanged(true);
+    }
+
+    @Test
+    public void addCallback_noConditions_reportAllConditionsMet() {
+        final Monitor monitor = new Monitor(new HashSet<>(), null /*callbacks*/);
+        final Monitor.Callback callback = mock(Monitor.Callback.class);
+
+        monitor.addCallback(callback);
+
+        verify(callback).onConditionsChanged(true);
+    }
+
+    @Test
+    public void removeCallback_shouldNoLongerReceiveUpdate() {
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback);
+        clearInvocations(callback);
+        mConditionMonitor.removeCallback(callback);
+
+        mConditionMonitor.overrideAllConditionsMet(true);
+        verify(callback, never()).onConditionsChanged(true);
+
+        mConditionMonitor.overrideAllConditionsMet(false);
+        verify(callback, never()).onConditionsChanged(false);
+    }
+
+    @Test
+    public void removeCallback_removeLastCallback_removeCallbackFromAllConditions() {
+        final Monitor.Callback callback1 =
+                mock(Monitor.Callback.class);
+        final Monitor.Callback callback2 =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback1);
+        mConditionMonitor.addCallback(callback2);
+
+        mConditionMonitor.removeCallback(callback1);
+        mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
+
+        mConditionMonitor.removeCallback(callback2);
+        mConditions.forEach(condition -> verify(condition).removeCallback(any()));
+    }
+
+    @Test
+    public void updateCallbacks_allConditionsMet_reportTrue() {
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback);
+        clearInvocations(callback);
+
+        mCondition1.fakeUpdateCondition(true);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+
+        verify(callback).onConditionsChanged(true);
+    }
+
+    @Test
+    public void updateCallbacks_oneConditionStoppedMeeting_reportFalse() {
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback);
+
+        mCondition1.fakeUpdateCondition(true);
+        mCondition2.fakeUpdateCondition(true);
+        mCondition3.fakeUpdateCondition(true);
+        clearInvocations(callback);
+
+        mCondition1.fakeUpdateCondition(false);
+        verify(callback).onConditionsChanged(false);
+    }
+
+    @Test
+    public void updateCallbacks_shouldOnlyUpdateWhenValueChanges() {
+        final Monitor.Callback callback =
+                mock(Monitor.Callback.class);
+        mConditionMonitor.addCallback(callback);
+        verify(callback).onConditionsChanged(false);
+        clearInvocations(callback);
+
+        mCondition1.fakeUpdateCondition(true);
+        verify(callback, never()).onConditionsChanged(anyBoolean());
+
+        mCondition2.fakeUpdateCondition(true);
+        verify(callback, never()).onConditionsChanged(anyBoolean());
+
+        mCondition3.fakeUpdateCondition(true);
+        verify(callback).onConditionsChanged(true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
similarity index 72%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
index 9d7ef0f..7fc6b51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.communal;
+package com.android.systemui.util.condition;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -29,7 +29,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.communal.conditions.CommunalCondition;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,26 +36,26 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
-public class CommunalConditionTest extends SysuiTestCase {
-    private FakeCommunalCondition mCondition;
+public class ConditionTest extends SysuiTestCase {
+    private FakeCondition mCondition;
 
     @Before
     public void setup() {
-        mCondition = spy(new FakeCommunalCondition());
+        mCondition = spy(new FakeCondition());
     }
 
     @Test
     public void addCallback_addFirstCallback_triggerStart() {
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         verify(mCondition).start();
     }
 
     @Test
     public void addCallback_addMultipleCallbacks_triggerStartOnlyOnce() {
-        final CommunalCondition.Callback callback1 = mock(CommunalCondition.Callback.class);
-        final CommunalCondition.Callback callback2 = mock(CommunalCondition.Callback.class);
-        final CommunalCondition.Callback callback3 = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback1 = mock(Condition.Callback.class);
+        final Condition.Callback callback2 = mock(Condition.Callback.class);
+        final Condition.Callback callback3 = mock(Condition.Callback.class);
 
         mCondition.addCallback(callback1);
         mCondition.addCallback(callback2);
@@ -67,19 +66,19 @@
 
     @Test
     public void addCallback_alreadyStarted_triggerUpdate() {
-        final CommunalCondition.Callback callback1 = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback1 = mock(Condition.Callback.class);
         mCondition.addCallback(callback1);
 
         mCondition.fakeUpdateCondition(true);
 
-        final CommunalCondition.Callback callback2 = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback2 = mock(Condition.Callback.class);
         mCondition.addCallback(callback2);
         verify(callback2).onConditionChanged(mCondition, true);
     }
 
     @Test
     public void removeCallback_removeLastCallback_triggerStop() {
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
         verify(mCondition, never()).stop();
 
@@ -91,7 +90,7 @@
     public void updateCondition_falseToTrue_reportTrue() {
         mCondition.fakeUpdateCondition(false);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(true);
@@ -102,7 +101,7 @@
     public void updateCondition_trueToFalse_reportFalse() {
         mCondition.fakeUpdateCondition(true);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(false);
@@ -113,7 +112,7 @@
     public void updateCondition_trueToTrue_reportNothing() {
         mCondition.fakeUpdateCondition(true);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(true);
@@ -124,7 +123,7 @@
     public void updateCondition_falseToFalse_reportNothing() {
         mCondition.fakeUpdateCondition(false);
 
-        final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class);
+        final Condition.Callback callback = mock(Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
similarity index 76%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
index 882effd..9d5ccbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.communal;
-
-import com.android.systemui.communal.conditions.CommunalCondition;
+package com.android.systemui.util.condition;
 
 /**
- * Fake implementation of {@link CommunalCondition}, and provides a way for tests to update
+ * Fake implementation of {@link Condition}, and provides a way for tests to update
  * condition fulfillment.
  */
-public class FakeCommunalCondition extends CommunalCondition {
+public class FakeCondition extends Condition {
     @Override
     public void start() {}
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index eb54fe0..0f1b65c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -44,6 +44,11 @@
 inline fun <reified T> any(): T = any(T::class.java)
 
 /**
+ * Kotlin type-inferred version of Mockito.nullable()
+ */
+inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java)
+
+/**
  * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
  * when null is returned.
  *
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java b/packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java
new file mode 100644
index 0000000..ff6fd6f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ref/GcWeakReference.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.ref;
+
+import com.android.internal.util.GcUtils;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * A WeakReference subclass that forces gc/finalizing on access.
+ */
+public class GcWeakReference<T> extends WeakReference<T> {
+    public GcWeakReference(T referent) {
+        super(referent);
+    }
+
+    @Override
+    public T get() throws RuntimeException {
+        GcUtils.runGcAndFinalizersSync();
+        return super.get();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 7c6d80b..22d7273 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -99,7 +99,7 @@
     @Test
     public void testConnect() {
         ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
-                mIntent, 0, mExecutor, mTransformer);
+                mIntent, mExecutor, mTransformer);
         // Register twice to ensure only one callback occurs.
         connection.addCallback(mCallback);
         connection.addCallback(mCallback);
@@ -119,7 +119,7 @@
     @Test
     public void testDisconnect() {
         ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
-                mIntent, 0, mExecutor, mTransformer);
+                mIntent, mExecutor, mTransformer);
         connection.addCallback(mCallback);
         connection.onServiceDisconnected(mComponentName);
 
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 a8e92f5..d27a570 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -99,8 +99,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -108,6 +108,7 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleData;
@@ -115,7 +116,6 @@
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleIconFactory;
 import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.BubbleViewInfoTask;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -124,6 +124,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.onehanded.OneHandedController;
 
 import com.google.common.collect.ImmutableList;
 
@@ -137,6 +138,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Tests the NotificationEntryManager setup with BubbleController.
@@ -243,9 +245,13 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private ScreenOffAnimationController mScreenOffAnimationController;
     @Mock
     private AuthController mAuthController;
+    @Mock
+    private TaskViewTransitions mTaskViewTransitions;
+    @Mock
+    private Optional<OneHandedController> mOneHandedOptional;
 
     private TestableBubblePositioner mPositioner;
 
@@ -269,7 +275,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager, mKeyguardStateController,
-                mUnlockedScreenOffAnimationController, mAuthController);
+                mScreenOffAnimationController, mAuthController);
         mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
@@ -342,8 +348,10 @@
                 mShellTaskOrganizer,
                 mPositioner,
                 mock(DisplayController.class),
+                mOneHandedOptional,
                 syncExecutor,
                 mock(Handler.class),
+                mTaskViewTransitions,
                 mock(SyncTransactionQueue.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
@@ -627,7 +635,7 @@
     }
 
     @Test
-    public void testRemoveLastExpanded_selectsOverflow() {
+    public void testRemoveLastExpanded_collapses() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onPendingEntryAdded(mRow);
         mEntryListener.onPendingEntryAdded(mRow2);
@@ -666,11 +674,10 @@
                         stackView.getExpandedBubble().getKey()).getKey(),
                 Bubbles.DISMISS_USER_GESTURE);
 
-        // Overflow should be selected
-        assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
-        assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        // We should be collapsed
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
+        assertFalse(mBubbleController.hasBubbles());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 8027390..ad26f01 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -85,21 +85,21 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DisplayController;
@@ -107,6 +107,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.onehanded.OneHandedController;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -118,6 +119,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Tests the NotifPipeline setup with BubbleController.
@@ -219,7 +221,11 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    private ScreenOffAnimationController mScreenOffAnimationController;
+    @Mock
+    private TaskViewTransitions mTaskViewTransitions;
+    @Mock
+    private Optional<OneHandedController> mOneHandedOptional;
 
     private TestableBubblePositioner mPositioner;
 
@@ -242,7 +248,7 @@
                 mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
                 mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
                 mColorExtractor, mDumpManager, mKeyguardStateController,
-                mUnlockedScreenOffAnimationController, mAuthController);
+                mScreenOffAnimationController, mAuthController);
         mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
         mNotificationShadeWindowController.attach();
 
@@ -307,8 +313,10 @@
                 mShellTaskOrganizer,
                 mPositioner,
                 mock(DisplayController.class),
+                mOneHandedOptional,
                 syncExecutor,
                 mock(Handler.class),
+                mTaskViewTransitions,
                 mock(SyncTransactionQueue.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
@@ -571,7 +579,7 @@
     }
 
     @Test
-    public void testRemoveLastExpanded_selectsOverflow() {
+    public void testRemoveLastExpanded_collapses() {
         // Mark it as a bubble and add it explicitly
         mEntryListener.onEntryAdded(mRow);
         mEntryListener.onEntryAdded(mRow2);
@@ -610,11 +618,10 @@
                         stackView.getExpandedBubble().getKey()).getKey(),
                 Bubbles.DISMISS_USER_GESTURE);
 
-        // Overflow should be selected
-        assertEquals(mBubbleData.getSelectedBubble().getKey(), BubbleOverflow.KEY);
-        verify(mBubbleExpandListener).onBubbleExpandChanged(true, BubbleOverflow.KEY);
-        assertTrue(mBubbleController.hasBubbles());
-        assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
+        // We should be collapsed
+        verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
+        assertFalse(mBubbleController.hasBubbles());
+        assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 7b77cb0..83f5987 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TaskViewTransitions;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
@@ -34,6 +35,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.onehanded.OneHandedController;
+
+import java.util.Optional;
 
 /**
  * Testable BubbleController subclass that immediately synchronizes surfaces.
@@ -54,13 +58,16 @@
             ShellTaskOrganizer shellTaskOrganizer,
             BubblePositioner positioner,
             DisplayController displayController,
+            Optional<OneHandedController> oneHandedOptional,
             ShellExecutor shellMainExecutor,
             Handler shellMainHandler,
+            TaskViewTransitions taskViewTransitions,
             SyncTransactionQueue syncQueue) {
         super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
                 statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
                 bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
-                shellMainExecutor, shellMainHandler, syncQueue);
+                oneHandedOptional, shellMainExecutor, shellMainHandler, taskViewTransitions,
+                syncQueue);
         setInflateSynchronously(true);
         initialize();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 1e15d2a..2f2e536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.wm.shell.ShellCommandHandler;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.compatui.CompatUI;
 import com.android.wm.shell.draganddrop.DragAndDrop;
 import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -42,7 +43,6 @@
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.sizecompatui.SizeCompatUI;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
 import org.junit.Before;
@@ -78,7 +78,7 @@
     @Mock WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock ProtoTracer mProtoTracer;
     @Mock ShellCommandHandler mShellCommandHandler;
-    @Mock SizeCompatUI mSizeCompatUI;
+    @Mock CompatUI mCompatUI;
     @Mock ShellExecutor mSysUiMainExecutor;
     @Mock DragAndDrop mDragAndDrop;
 
@@ -88,7 +88,7 @@
 
         mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
                 Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
-                Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI),
+                Optional.of(mShellCommandHandler), Optional.of(mCompatUI),
                 Optional.of(mDragAndDrop),
                 mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor,
                 mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer,
@@ -136,8 +136,8 @@
     }
 
     @Test
-    public void initSizeCompatUI_registersCallbacks() {
-        mWMShell.initSizeCompatUi(mSizeCompatUI);
+    public void initCompatUI_registersCallbacks() {
+        mWMShell.initCompatUi(mCompatUI);
 
         verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
     }
diff --git a/services/Android.bp b/services/Android.bp
index 82d9b3e..c830c22 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -26,6 +26,36 @@
     },
 }
 
+// Opt-in config for optimizing and shrinking the services target using R8.
+// Enabled via `export SYSTEM_OPTIMIZE_JAVA=true`, or explicitly in Make via the
+// `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA` variable.
+// TODO(b/196084106): Enable optimizations by default after stabilizing and
+// building out retrace infrastructure.
+soong_config_module_type {
+    name: "system_optimized_java_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: ["SYSTEM_OPTIMIZE_JAVA"],
+    properties: ["optimize"],
+}
+
+system_optimized_java_defaults {
+    name: "services_java_defaults",
+    soong_config_variables: {
+        SYSTEM_OPTIMIZE_JAVA: {
+            optimize: {
+                enabled: true,
+                optimize: true,
+                shrink: true,
+                proguard_flags_files: ["proguard.flags"],
+            },
+            // Note: Optimizations are disabled by default if unspecified in
+            // the java_library rule.
+            conditions_default: {},
+        },
+    },
+}
+
 filegroup {
     name: "services-main-sources",
     srcs: [
@@ -60,6 +90,7 @@
         ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.searchui-sources",
+        ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
         ":services.speech-sources",
         ":services.startop.iorap-sources",
@@ -83,6 +114,7 @@
 // ============================================================
 java_library {
     name: "services",
+    defaults: ["services_java_defaults"],
     installable: true,
 
     dex_preopt: {
@@ -113,6 +145,7 @@
         "services.profcollect",
         "services.restrictions",
         "services.searchui",
+        "services.selectiontoolbar",
         "services.smartspace",
         "services.speech",
         "services.startop",
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index 1698e9a..bf8a9af 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -16,7 +16,15 @@
 
 java_library_static {
     name: "services.accessibility",
-    defaults: ["platform_service_defaults"],
-    srcs: [":services.accessibility-sources"],
-    libs: ["services.core"],
+    defaults: [
+        "platform_service_defaults",
+    ],
+    srcs: [
+        ":services.accessibility-sources",
+        "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
+    ],
+    libs: [
+        "services.core",
+        "androidx.annotation_annotation",
+    ],
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f1599e4..f050b66 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -976,6 +976,25 @@
         return false;
     }
 
+    @Nullable
+    @Override
+    public MagnificationConfig getMagnificationConfig(int displayId) {
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("getMagnificationConfig", "displayId=" + displayId);
+        }
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return null;
+            }
+        }
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return mSystemSupport.getMagnificationProcessor().getMagnificationConfig(displayId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
     @Override
     public float getMagnificationScale(int displayId) {
         if (svcConnTracingEnabled()) {
@@ -1008,8 +1027,8 @@
                     mSystemSupport.getMagnificationProcessor();
             final long identity = Binder.clearCallingIdentity();
             try {
-                magnificationProcessor.getMagnificationRegion(displayId, region,
-                        mSecurityPolicy.canControlMagnification(this));
+                magnificationProcessor.getFullscreenMagnificationRegion(displayId,
+                        region, mSecurityPolicy.canControlMagnification(this));
                 return region;
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1076,7 +1095,7 @@
         try {
             MagnificationProcessor magnificationProcessor =
                     mSystemSupport.getMagnificationProcessor();
-            return (magnificationProcessor.reset(displayId, animate)
+            return (magnificationProcessor.resetFullscreenMagnification(displayId, animate)
                     || !magnificationProcessor.isMagnifying(displayId));
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -1084,12 +1103,11 @@
     }
 
     @Override
-    public boolean setMagnificationScaleAndCenter(int displayId, float scale, float centerX,
-            float centerY, boolean animate) {
+    public boolean setMagnificationConfig(int displayId,
+            @NonNull MagnificationConfig config, boolean animate) {
         if (svcConnTracingEnabled()) {
-            logTraceSvcConn("setMagnificationScaleAndCenter",
-                    "displayId=" + displayId + ";scale=" + scale + ";centerX=" + centerX
-                    + ";centerY=" + centerY + ";animate=" + animate);
+            logTraceSvcConn("setMagnificationSpec",
+                    "displayId=" + displayId + ", config=" + config.toString());
         }
         synchronized (mLock) {
             if (!hasRightsToCurrentUserLocked()) {
@@ -1102,10 +1120,6 @@
             try {
                 MagnificationProcessor magnificationProcessor =
                         mSystemSupport.getMagnificationProcessor();
-                final MagnificationConfig config = new MagnificationConfig.Builder()
-                        .setScale(scale)
-                        .setCenterX(centerX)
-                        .setCenterY(centerY).build();
                 return magnificationProcessor.setMagnificationConfig(displayId, config, animate,
                         mId);
             } finally {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 0ab0c89..e251bcc 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -16,13 +16,18 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_BY_ADMIN;
+import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
 import static android.content.pm.PackageManagerInternal.PACKAGE_INSTALLER;
 
 import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManager;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -41,13 +46,17 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.inputmethod.InputMethodInfo;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+import com.android.settingslib.RestrictedLockUtils;
 
 import libcore.util.EmptyArray;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -364,6 +373,115 @@
     }
 
     /**
+     * Check whether the input method can be enabled or disabled by the accessibility service.
+     *
+     * @param imeId The id of the input method.
+     * @param service The accessibility service connection.
+     * @return Whether the input method can be enabled/disabled or the reason why it can't be
+     *         enabled/disabled.
+     * @throws SecurityException if the input method is not in the same package as the service.
+     */
+    @AccessibilityService.SoftKeyboardController.EnableImeResult
+    int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service)
+            throws SecurityException {
+        final String servicePackageName = service.getComponentName().getPackageName();
+        final int callingUserId = UserHandle.getCallingUserId();
+
+        InputMethodInfo inputMethodInfo = null;
+        List<InputMethodInfo> inputMethodInfoList =
+                InputMethodManagerInternal.get().getInputMethodListAsUser(callingUserId);
+        if (inputMethodInfoList != null) {
+            for (InputMethodInfo info : inputMethodInfoList) {
+                if (info.getId().equals(imeId)) {
+                    inputMethodInfo = info;
+                    break;
+                }
+            }
+        }
+
+        if (inputMethodInfo == null
+                || !inputMethodInfo.getPackageName().equals(servicePackageName)) {
+            throw new SecurityException("The input method is in a different package with the "
+                    + "accessibility service");
+        }
+
+        // TODO(b/207697949, b/208872785): Add cts test for managed device.
+        //  Use RestrictedLockUtilsInternal in AccessibilitySecurityPolicy
+        if (checkIfInputMethodDisallowed(
+                mContext, inputMethodInfo.getPackageName(), callingUserId) != null) {
+            return ENABLE_IME_FAIL_BY_ADMIN;
+        }
+
+        return ENABLE_IME_SUCCESS;
+    }
+
+    /**
+     * @return the UserHandle for a userId. Return null for USER_NULL
+     */
+    private static UserHandle getUserHandleOf(@UserIdInt int userId) {
+        if (userId == UserHandle.USER_NULL) {
+            return null;
+        } else {
+            return UserHandle.of(userId);
+        }
+    }
+
+    private static int getManagedProfileId(Context context, int userId) {
+        UserManager um = context.getSystemService(UserManager.class);
+        List<UserInfo> userProfiles = um.getProfiles(userId);
+        for (UserInfo uInfo : userProfiles) {
+            if (uInfo.id == userId) {
+                continue;
+            }
+            if (uInfo.isManagedProfile()) {
+                return uInfo.id;
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
+    private static RestrictedLockUtils.EnforcedAdmin checkIfInputMethodDisallowed(Context context,
+            String packageName, int userId) {
+        DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+        if (dpm == null) {
+            return null;
+        }
+        RestrictedLockUtils.EnforcedAdmin admin =
+                RestrictedLockUtils.getProfileOrDeviceOwner(context, getUserHandleOf(userId));
+        boolean permitted = true;
+        if (admin != null) {
+            permitted = dpm.isInputMethodPermittedByAdmin(admin.component,
+                    packageName, userId);
+        }
+
+        boolean permittedByParentAdmin = true;
+        RestrictedLockUtils.EnforcedAdmin profileAdmin = null;
+        int managedProfileId = getManagedProfileId(context, userId);
+        if (managedProfileId != UserHandle.USER_NULL) {
+            profileAdmin = RestrictedLockUtils.getProfileOrDeviceOwner(
+                    context, getUserHandleOf(managedProfileId));
+            // If the device is an organization-owned device with a managed profile, the
+            // managedProfileId will be used instead of the affected userId. This is because
+            // isInputMethodPermittedByAdmin is called on the parent DPM instance, which will
+            // return results affecting the personal profile.
+            if (profileAdmin != null && dpm.isOrganizationOwnedDeviceWithManagedProfile()) {
+                DevicePolicyManager parentDpm = dpm.getParentProfileInstance(
+                        UserManager.get(context).getUserInfo(managedProfileId));
+                permittedByParentAdmin = parentDpm.isInputMethodPermittedByAdmin(
+                        profileAdmin.component, packageName, managedProfileId);
+            }
+        }
+        if (!permitted && !permittedByParentAdmin) {
+            return RestrictedLockUtils.EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN;
+        } else if (!permitted) {
+            return admin;
+        } else if (!permittedByParentAdmin) {
+            return profileAdmin;
+        }
+        return null;
+    }
+
+    /**
      * Returns the parent userId of the profile according to the specified userId.
      *
      * @param userId The userId to check
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index bcb3413..8f7260f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,9 +16,13 @@
 
 package com.android.server.accessibility;
 
+import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
+import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
+
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.Manifest;
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -310,6 +314,41 @@
     }
 
     @Override
+    @AccessibilityService.SoftKeyboardController.EnableImeResult
+    public int setInputMethodEnabled(String imeId, boolean enabled) throws SecurityException {
+        if (svcConnTracingEnabled()) {
+            logTraceSvcConn("switchToInputMethod", "imeId=" + imeId);
+        }
+        synchronized (mLock) {
+            if (!hasRightsToCurrentUserLocked()) {
+                return ENABLE_IME_FAIL_UNKNOWN;
+            }
+        }
+
+        final int callingUserId = UserHandle.getCallingUserId();
+        final InputMethodManagerInternal inputMethodManagerInternal =
+                InputMethodManagerInternal.get();
+
+        final @AccessibilityService.SoftKeyboardController.EnableImeResult int checkResult;
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this);
+            }
+            if (checkResult != ENABLE_IME_SUCCESS) {
+                return checkResult;
+            }
+            if (inputMethodManagerInternal.setInputMethodEnabled(imeId,
+                        enabled, callingUserId)) {
+                return ENABLE_IME_SUCCESS;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return ENABLE_IME_FAIL_UNKNOWN;
+    }
+
+    @Override
     public boolean isAccessibilityButtonAvailable() {
         if (svcConnTracingEnabled()) {
             logTraceSvcConn("isAccessibilityButtonAvailable", "");
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index eaf2694..6744ea8 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -288,8 +288,6 @@
                     showGlobalActions();
                     return true;
                 }
-                case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN:
-                    return toggleSplitScreen();
                 case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN:
                     return lockScreen();
                 case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT:
@@ -369,21 +367,6 @@
         mWindowManagerService.showGlobalActions();
     }
 
-    private boolean toggleSplitScreen() {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            StatusBarManagerInternal statusBarService = LocalServices.getService(
-                    StatusBarManagerInternal.class);
-            if (statusBarService == null) {
-                return false;
-            }
-            statusBarService.toggleSplitScreen();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-        return true;
-    }
-
     private boolean lockScreen() {
         mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
                 PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7ee0690..6cc2214 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -333,6 +334,11 @@
         }
 
         @Override
+        public int setInputMethodEnabled(String imeId, boolean enabled) {
+            return AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
+        }
+
+        @Override
         public boolean isAccessibilityButtonAvailable() {
             return false;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index 51e3417..38615fe 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -63,7 +63,8 @@
  * magnification region. If a value is out of bounds, it will be adjusted to guarantee these
  * constraints.
  */
-public class FullScreenMagnificationController {
+public class FullScreenMagnificationController implements
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "FullScreenMagnificationController";
 
@@ -85,6 +86,8 @@
     @GuardedBy("mLock")
     private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
 
+    private final Rect mTempRect = new Rect();
+
     /**
      * This class implements {@link WindowManagerInternal.MagnificationCallbacks} and holds
      * magnification information per display.
@@ -727,6 +730,26 @@
         }
     }
 
+    @Override
+    public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
+            int bottom) {
+        synchronized (mLock) {
+            final DisplayMagnification display = mDisplays.get(displayId);
+            if (display == null) {
+                return;
+            }
+            if (!display.isMagnifying()) {
+                return;
+            }
+            final Rect magnifiedRegionBounds = mTempRect;
+            display.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
+            if (magnifiedRegionBounds.contains(left, top, right, bottom)) {
+                return;
+            }
+            display.onRectangleOnScreenRequested(left, top, right, bottom);
+        }
+    }
+
     /**
      * Remove the display magnification with given id.
      *
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 42a81e1..791da69 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -40,7 +40,9 @@
 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.wm.WindowManagerInternal;
 
 /**
  * Handles all magnification controllers initialization, generic interactions,
@@ -68,7 +70,8 @@
  */
 public class MagnificationController implements WindowMagnificationManager.Callback,
         MagnificationGestureHandler.Callback,
-        FullScreenMagnificationController.MagnificationInfoChangedCallback {
+        FullScreenMagnificationController.MagnificationInfoChangedCallback,
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
 
     private static final boolean DEBUG = false;
     private static final String TAG = "MagnificationController";
@@ -96,6 +99,11 @@
     private long mWindowModeEnabledTime = 0;
     private long mFullScreenModeEnabledTime = 0;
 
+    @GuardedBy("mLock")
+    @Nullable
+    private WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
+            mAccessibilityCallbacksDelegate;
+
     /**
      * A callback to inform the magnification transition result on the given display.
      */
@@ -115,6 +123,8 @@
         mLock = lock;
         mContext = context;
         mScaleProvider = scaleProvider;
+        LocalServices.getService(WindowManagerInternal.class)
+                .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
     }
 
     @VisibleForTesting
@@ -292,6 +302,37 @@
         return false;
     }
 
+    @GuardedBy("mLock")
+    private void setActivatedModeAndSwitchDelegate(int mode) {
+        mActivatedMode = mode;
+        assignMagnificationWindowManagerDelegateByMode(mode);
+    }
+
+    private void assignMagnificationWindowManagerDelegateByMode(int mode) {
+        synchronized (mLock) {
+            if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN) {
+                mAccessibilityCallbacksDelegate = getFullScreenMagnificationController();
+            } else if (mode == ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
+                mAccessibilityCallbacksDelegate = getWindowMagnificationMgr();
+            } else {
+                mAccessibilityCallbacksDelegate = null;
+            }
+        }
+    }
+
+    @Override
+    public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
+            int bottom) {
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
+                delegate;
+        synchronized (mLock) {
+            delegate = mAccessibilityCallbacksDelegate;
+        }
+        if (delegate != null) {
+            delegate.onRectangleOnScreenRequested(displayId, left, top, right, bottom);
+        }
+    }
+
     @Override
     public void onRequestMagnificationSpec(int displayId, int serviceId) {
         final WindowMagnificationManager windowMagnificationManager;
@@ -314,7 +355,7 @@
             mWindowModeEnabledTime = SystemClock.uptimeMillis();
 
             synchronized (mLock) {
-                mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+                setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
                 mLastActivatedMode = mActivatedMode;
             }
             logMagnificationModeWithImeOnIfNeeded();
@@ -324,7 +365,7 @@
                     SystemClock.uptimeMillis() - mWindowModeEnabledTime);
 
             synchronized (mLock) {
-                mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+                setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
             }
         }
         updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
@@ -353,7 +394,7 @@
             mFullScreenModeEnabledTime = SystemClock.uptimeMillis();
 
             synchronized (mLock) {
-                mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+                setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
                 mLastActivatedMode = mActivatedMode;
             }
             logMagnificationModeWithImeOnIfNeeded();
@@ -363,7 +404,7 @@
                     SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
 
             synchronized (mLock) {
-                mActivatedMode = ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
+                setActivatedModeAndSwitchDelegate(ACCESSIBILITY_MAGNIFICATION_MODE_NONE);
             }
         }
         updateMagnificationButton(displayId, ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
@@ -681,7 +722,7 @@
             } else {
                 getWindowMagnificationMgr().enableWindowMagnification(mDisplayId,
                         mCurrentScale, mCurrentCenter.x,
-                        mCurrentCenter.y);
+                        mCurrentCenter.y, mAnimate ? STUB_ANIMATION_CALLBACK : null);
             }
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index cd338a4..7a525ee 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -21,6 +21,7 @@
 import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
 
 import android.accessibilityservice.MagnificationConfig;
 import android.annotation.NonNull;
@@ -112,11 +113,24 @@
                     animate, id);
         } else if (configMode == MAGNIFICATION_MODE_WINDOW) {
             return mController.getWindowMagnificationMgr().enableWindowMagnification(displayId,
-                    config.getScale(), config.getCenterX(), config.getCenterY());
+                    config.getScale(), config.getCenterX(), config.getCenterY(),
+                    animate ? STUB_ANIMATION_CALLBACK : null);
         }
         return false;
     }
 
+    private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
+            float centerX, float centerY,
+            boolean animate, int id) {
+        if (!isRegistered(displayId)) {
+            register(displayId);
+        }
+        return mController.getFullScreenMagnificationController().setScaleAndCenter(
+                displayId,
+                scale,
+                centerX, centerY, animate, id);
+    }
+
     /**
      * Returns {@code true} if transition magnification mode needed. And it is no need to transition
      * mode when the controlling mode is unchanged or the controlling magnifier is not activated.
@@ -133,24 +147,18 @@
     }
 
     /**
-     * Returns the magnification scale. If an animation is in progress,
-     * this reflects the end state of the animation.
+     * Returns the magnification scale of full-screen magnification on the display.
+     * If an animation is in progress, this reflects the end state of the animation.
      *
      * @param displayId The logical display id.
      * @return the scale
      */
     public float getScale(int displayId) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            return mController.getFullScreenMagnificationController().getScale(displayId);
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().getScale(displayId);
-        }
-        return 0;
+        return mController.getFullScreenMagnificationController().getScale(displayId);
     }
 
     /**
-     * Returns the magnification center in X coordinate of the controlling magnification mode.
+     * Returns the magnification center in X coordinate of full-screen magnification.
      * If the service can control magnification but fullscreen magnifier is not registered, it will
      * register the magnifier for this call then unregister the magnifier finally to make the
      * magnification center correct.
@@ -160,25 +168,19 @@
      * @return the X coordinate
      */
     public float getCenterX(int displayId, boolean canControlMagnification) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
-                    canControlMagnification);
-            try {
-                return mController.getFullScreenMagnificationController().getCenterX(displayId);
-            } finally {
-                if (registeredJustForThisCall) {
-                    unregister(displayId);
-                }
+        boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+                canControlMagnification);
+        try {
+            return mController.getFullScreenMagnificationController().getCenterX(displayId);
+        } finally {
+            if (registeredJustForThisCall) {
+                unregister(displayId);
             }
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().getCenterX(displayId);
         }
-        return 0;
     }
 
     /**
-     * Returns the magnification center in Y coordinate of the controlling magnification mode.
+     * Returns the magnification center in Y coordinate of full-screen magnification.
      * If the service can control magnification but fullscreen magnifier is not registered, it will
      * register the magnifier for this call then unregister the magnifier finally to make the
      * magnification center correct.
@@ -188,49 +190,25 @@
      * @return the Y coordinate
      */
     public float getCenterY(int displayId, boolean canControlMagnification) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
-                    canControlMagnification);
-            try {
-                return mController.getFullScreenMagnificationController().getCenterY(displayId);
-            } finally {
-                if (registeredJustForThisCall) {
-                    unregister(displayId);
-                }
+        boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
+                canControlMagnification);
+        try {
+            return mController.getFullScreenMagnificationController().getCenterY(displayId);
+        } finally {
+            if (registeredJustForThisCall) {
+                unregister(displayId);
             }
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            return mController.getWindowMagnificationMgr().getCenterY(displayId);
         }
-        return 0;
     }
 
     /**
-     * Return the magnification bounds of the current controlling magnification on the given
-     * display. If the magnifier is not enabled, it returns an empty region.
-     * If the service can control magnification but fullscreen magnifier is not registered, it will
-     * register the magnifier for this call then unregister the magnifier finally to make
-     * the magnification region correct.
+     * Returns the magnification bounds of full-screen magnification on the given display.
      *
      * @param displayId The logical display id
      * @param outRegion the region to populate
      * @param canControlMagnification Whether the service can control magnification
-     * @return outRegion the magnification bounds of full-screen magnifier or the magnification
-     * source bounds of window magnifier
      */
-    public Region getMagnificationRegion(int displayId, @NonNull Region outRegion,
-            boolean canControlMagnification) {
-        int mode = getControllingMode(displayId);
-        if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
-            getFullscreenMagnificationRegion(displayId, outRegion, canControlMagnification);
-        } else if (mode == MAGNIFICATION_MODE_WINDOW) {
-            mController.getWindowMagnificationMgr().getMagnificationSourceBounds(displayId,
-                    outRegion);
-        }
-        return outRegion;
-    }
-
-    private void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
+    public void getFullscreenMagnificationRegion(int displayId, @NonNull Region outRegion,
             boolean canControlMagnification) {
         boolean registeredJustForThisCall = registerDisplayMagnificationIfNeeded(displayId,
                 canControlMagnification);
@@ -244,21 +222,9 @@
         }
     }
 
-    private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
-            float centerX, float centerY,
-            boolean animate, int id) {
-        if (!isRegistered(displayId)) {
-            register(displayId);
-        }
-        return mController.getFullScreenMagnificationController().setScaleAndCenter(
-                displayId,
-                scale,
-                centerX, centerY, animate, id);
-    }
-
     /**
-     * Resets the magnification on the given display. The reset mode could be full-screen or
-     * window if it is activated.
+     * Resets the current magnification on the given display. The reset mode could be
+     * full-screen or window if it is activated.
      *
      * @param displayId The logical display id.
      * @param animate   {@code true} to animate the transition, {@code false}
@@ -266,7 +232,7 @@
      * @return {@code true} if the magnification spec changed, {@code false} if
      * the spec did not change
      */
-    public boolean reset(int displayId, boolean animate) {
+    public boolean resetCurrentMagnification(int displayId, boolean animate) {
         int mode = getControllingMode(displayId);
         if (mode == MAGNIFICATION_MODE_FULLSCREEN) {
             return mController.getFullScreenMagnificationController().reset(displayId, animate);
@@ -277,6 +243,19 @@
     }
 
     /**
+     * Resets the full-screen magnification on the given display.
+     *
+     * @param displayId The logical display id.
+     * @param animate   {@code true} to animate the transition, {@code false}
+     *                  to transition immediately
+     * @return {@code true} if the magnification spec changed, {@code false} if
+     * the spec did not change
+     */
+    public boolean resetFullscreenMagnification(int displayId, boolean animate) {
+        return mController.getFullScreenMagnificationController().reset(displayId, animate);
+    }
+
+    /**
      * {@link FullScreenMagnificationController#resetIfNeeded(int, boolean)}
      */
     // TODO: support window magnification
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 9162064..3f6ff25 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -45,6 +45,7 @@
 import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -57,7 +58,8 @@
  * {@link MagnificationScaleProvider#constrainScale(float)}
  */
 public class WindowMagnificationManager implements
-        PanningScalingHandler.MagnificationDelegate {
+        PanningScalingHandler.MagnificationDelegate,
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
 
     private static final boolean DBG = false;
 
@@ -264,6 +266,13 @@
     }
 
     @Override
+    public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
+            int bottom) {
+        // TODO(b/194668976): We will implement following typing focus in window mode after
+        //  our refactor.
+    }
+
+    @Override
     public boolean processScroll(int displayId, float distanceX, float distanceY) {
         moveWindowMagnification(displayId, -distanceX, -distanceY);
         return /* event consumed: */ true;
diff --git a/services/art-profile b/services/art-profile
index af58bca..2d9e95e 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -1464,7 +1464,7 @@
 HPLcom/android/server/DeviceIdleController;->resetIdleManagementLocked()V+]Lcom/android/server/AnyMotionDetector;Lcom/android/server/AnyMotionDetector;]Lcom/android/server/DeviceIdleController;Lcom/android/server/DeviceIdleController;
 HPLcom/android/server/DeviceIdleController;->resetLightIdleManagementLocked()V+]Lcom/android/server/DeviceIdleController;Lcom/android/server/DeviceIdleController;
 HPLcom/android/server/DeviceIdleController;->scheduleAlarmLocked(JZ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
-HPLcom/android/server/DeviceIdleController;->scheduleLightAlarmLocked(JJ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
+HPLcom/android/server/DeviceIdleController;->scheduleLightAlarmLocked(JJZ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
 HPLcom/android/server/DeviceIdleController;->scheduleMotionRegistrationAlarmLocked()V+]Lcom/android/server/DeviceIdleController$Injector;Lcom/android/server/DeviceIdleController$Injector;]Landroid/app/AlarmManager;Landroid/app/AlarmManager;
 HSPLcom/android/server/DeviceIdleController;->scheduleMotionTimeoutAlarmLocked()V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;]Lcom/android/server/DeviceIdleController$Injector;Lcom/android/server/DeviceIdleController$Injector;
 HPLcom/android/server/DeviceIdleController;->scheduleReportActiveLocked(Ljava/lang/String;I)V+]Lcom/android/server/DeviceIdleController$MyHandler;Lcom/android/server/DeviceIdleController$MyHandler;
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index 594140e..21a22f4 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -39,6 +39,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.Preconditions;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.OnTransportRegisteredListener;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportConnectionManager;
@@ -641,7 +642,7 @@
         TransportConnection transportConnection =
                 mTransportConnectionManager.getTransportClient(
                         transportComponent, extras, callerLogString);
-        final IBackupTransport transport;
+        final BackupTransportClient transport;
         try {
             transport = transportConnection.connectOrThrow(callerLogString);
         } catch (TransportNotAvailableException e) {
@@ -653,10 +654,6 @@
 
         int result;
         try {
-            // This is a temporary fix to allow blocking calls.
-            // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
-            Binder.allowBlocking(transport.asBinder());
-
             String transportName = transport.name();
             String transportDirName = transport.transportDirName();
             registerTransport(transportComponent, transport);
@@ -674,8 +671,8 @@
     }
 
     /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
-    private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
-            throws RemoteException {
+    private void registerTransport(ComponentName transportComponent,
+            BackupTransportClient transport) throws RemoteException {
         checkCanUseTransport();
 
         TransportDescription description =
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index a3f6eb6..85ab48c 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -21,6 +21,7 @@
 import android.app.backup.RestoreSet;
 import android.content.Intent;
 import android.content.pm.PackageInfo;
+import android.os.Binder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 
@@ -35,6 +36,10 @@
 
     BackupTransportClient(IBackupTransport transportBinder) {
         mTransportBinder = transportBinder;
+
+        // This is a temporary fix to allow blocking calls.
+        // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
+        Binder.allowBlocking(mTransportBinder.asBinder());
     }
 
     /**
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
index da77eba..f9a3c36 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
@@ -59,7 +59,7 @@
 import java.util.concurrent.ExecutionException;
 
 /**
- * A {@link TransportConnection} manages the connection to an {@link IBackupTransport} service,
+ * A {@link TransportConnection} manages the connection to a {@link BackupTransportClient},
  * obtained via the {@param bindIntent} parameter provided in the constructor. A
  * {@link TransportConnection} is responsible for only one connection to the transport service,
  * not more.
@@ -67,9 +67,9 @@
  * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
  * call either {@link #connect(String)}, if you can block your thread, or {@link
  * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
- * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
- * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
- * via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}.
+ * BackupTransportClient} instance. It's meant to be passed around as a token to a connected
+ * transport. When the connection is not needed anymore you should call {@link #unbind(String)} or
+ * indirectly via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}.
  *
  * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
  *
@@ -106,7 +106,7 @@
     private int mState = State.IDLE;
 
     @GuardedBy("mStateLock")
-    private volatile IBackupTransport mTransport;
+    private volatile BackupTransportClient mTransport;
 
     TransportConnection(
             @UserIdInt int userId,
@@ -174,10 +174,12 @@
      * trigger another one, just piggyback on the original request.
      *
      * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
-     * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
-     * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
-     * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
-     * The reasons for a null transport binder are:
+     * call. However, the {@link BackupTransportClient} parameter in
+     * {@link TransportConnectionListener#onTransportConnectionResult(BackupTransportClient,
+     * TransportConnection)}, the transport client, is not guaranteed to be non-null, or if it's
+     * non-null it's not guaranteed to be usable - i.e. it can throw {@link DeadObjectException}s
+     * on method calls. You should check for both in your code. The reasons for a null transport
+     * client are:
      *
      * <ul>
      *   <li>Some code called {@link #unbind(String)} before you got a callback.
@@ -193,7 +195,7 @@
      * For unusable transport binders check {@link DeadObjectException}.
      *
      * @param listener The listener that will be called with the (possibly null or unusable) {@link
-     *     IBackupTransport} instance and this {@link TransportConnection} object.
+     *     BackupTransportClient} instance and this {@link TransportConnection} object.
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
      *     should be a human-readable short string that is easily identifiable in the logs. Ideally
      *     TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
@@ -293,8 +295,8 @@
      *
      * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
      * same observations about state are valid here. Also, what was said about the {@link
-     * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
-     * value of this method.
+     * BackupTransportClient} parameter of {@link TransportConnectionListener} now apply to the
+     * return value of this method.
      *
      * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
      * threads. You can't call this from the process main-thread (it throws an exception if you do
@@ -305,18 +307,18 @@
      *
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
      *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
-     * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
-     *     still be unusable - throws {@link DeadObjectException} on method calls
+     * @return A {@link BackupTransportClient} transport client instance or null. If it's non-null
+     *     it can still be unusable - throws {@link DeadObjectException} on method calls
      */
     @WorkerThread
     @Nullable
-    public IBackupTransport connect(String caller) {
+    public BackupTransportClient connect(String caller) {
         // If called on the main-thread this could deadlock waiting because calls to
         // ServiceConnection are on the main-thread as well
         Preconditions.checkState(
                 !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
 
-        IBackupTransport transport = mTransport;
+        BackupTransportClient transport = mTransport;
         if (transport != null) {
             log(Priority.DEBUG, caller, "Sync connect: reusing transport");
             return transport;
@@ -330,7 +332,7 @@
             }
         }
 
-        CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+        CompletableFuture<BackupTransportClient> transportFuture = new CompletableFuture<>();
         TransportConnectionListener requestListener =
                 (requestedTransport, transportClient) ->
                         transportFuture.complete(requestedTransport);
@@ -359,13 +361,14 @@
      *
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
      *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
-     * @return A {@link IBackupTransport} transport binder instance.
+     * @return A {@link BackupTransportClient} transport binder instance.
      * @see #connect(String)
      * @throws TransportNotAvailableException if connection attempt fails.
      */
     @WorkerThread
-    public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
-        IBackupTransport transport = connect(caller);
+    public BackupTransportClient connectOrThrow(String caller)
+            throws TransportNotAvailableException {
+        BackupTransportClient transport = connect(caller);
         if (transport == null) {
             log(Priority.ERROR, caller, "Transport connection failed");
             throw new TransportNotAvailableException();
@@ -379,12 +382,12 @@
      *
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
      *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
-     * @return A {@link IBackupTransport} transport binder instance.
+     * @return A {@link BackupTransportClient} transport client instance.
      * @throws TransportNotAvailableException if not connected.
      */
-    public IBackupTransport getConnectedTransport(String caller)
+    public BackupTransportClient getConnectedTransport(String caller)
             throws TransportNotAvailableException {
-        IBackupTransport transport = mTransport;
+        BackupTransportClient transport = mTransport;
         if (transport == null) {
             log(Priority.ERROR, caller, "Transport not connected");
             throw new TransportNotAvailableException();
@@ -425,7 +428,8 @@
     }
 
     private void onServiceConnected(IBinder binder) {
-        IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+        IBackupTransport transportBinder = IBackupTransport.Stub.asInterface(binder);
+        BackupTransportClient transport = new BackupTransportClient(transportBinder);
         synchronized (mStateLock) {
             checkStateIntegrityLocked();
 
@@ -492,15 +496,15 @@
 
     private void notifyListener(
             TransportConnectionListener listener,
-            @Nullable IBackupTransport transport,
+            @Nullable BackupTransportClient transport,
             String caller) {
-        String transportString = (transport != null) ? "IBackupTransport" : "null";
+        String transportString = (transport != null) ? "BackupTransportClient" : "null";
         log(Priority.INFO, "Notifying [" + caller + "] transport = " + transportString);
         mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
     }
 
     @GuardedBy("mStateLock")
-    private void notifyListenersAndClearLocked(@Nullable IBackupTransport transport) {
+    private void notifyListenersAndClearLocked(@Nullable BackupTransportClient transport) {
         for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
             TransportConnectionListener listener = entry.getKey();
             String caller = entry.getValue();
@@ -510,7 +514,7 @@
     }
 
     @GuardedBy("mStateLock")
-    private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+    private void setStateLocked(@State int state, @Nullable BackupTransportClient transport) {
         log(Priority.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
         onStateTransition(mState, state);
         mState = state;
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
index 03d35e4..1776c41 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
@@ -18,7 +18,7 @@
 
 import android.annotation.Nullable;
 
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.transport.BackupTransportClient;
 
 /**
  * Listener to be called by {@link TransportConnection#connectAsync(TransportConnectionListener,
@@ -26,13 +26,14 @@
  */
 public interface TransportConnectionListener {
     /**
-     * Called when {@link TransportConnection} has a transport binder available or that it decided
+     * Called when {@link TransportConnection} has a transport client available or that it decided
      * it couldn't obtain one, in which case {@param transport} is null.
      *
-     * @param transport A {@link IBackupTransport} transport binder or null.
+     * @param transportClient A {@link BackupTransportClient} transport or null.
      * @param transportConnection The {@link TransportConnection} used to retrieve this transport
-     *                            binder.
+     *                            client.
      */
     void onTransportConnectionResult(
-            @Nullable IBackupTransport transport, TransportConnection transportConnection);
+            @Nullable BackupTransportClient transportClient,
+            TransportConnection transportConnection);
 }
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index 0855b9d..0fe90b1 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -6,7 +6,6 @@
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 
 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
@@ -22,6 +21,7 @@
 import android.os.SELinux;
 import android.util.Slog;
 
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.fullbackup.AppMetadataBackupWriter;
 import com.android.server.backup.remote.ServiceBackupCallback;
 import com.android.server.backup.utils.FullBackupUtils;
@@ -162,7 +162,7 @@
         long kvBackupAgentTimeoutMillis = mAgentTimeoutParameters.getKvBackupAgentTimeoutMillis();
         try {
             mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
-                    OP_TYPE_BACKUP_WAIT);
+                    OpType.BACKUP_WAIT);
 
             IBackupCallback callback =
                     new ServiceBackupCallback(
@@ -262,7 +262,7 @@
             pipes = ParcelFileDescriptor.createPipe();
 
             mBackupManagerService.prepareOperationTimeout(token, kvBackupAgentTimeoutMillis, null,
-                    OP_TYPE_BACKUP_WAIT);
+                    OpType.BACKUP_WAIT);
 
             // We will have to create a runnable that will read the manifest and backup data we
             // created, such that we can pipe the data into mOutput. The reason we do this is that
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
new file mode 100644
index 0000000..466f647
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -0,0 +1,156 @@
+/*
+ * 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.backup;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * OperationStorage is an abstraction around a set of active operations.
+ *
+ * Operations are registered with a token that must first be obtained from
+ * {@link UserBackupManagerService#generateRandomIntegerToken()}.  When
+ * registering, the caller may also associate a set of package names with
+ * the operation.
+ *
+ * TODO(b/208442527): have the token be generated within and returned by
+ *                    registerOperation, as it should be an internal detail.
+ *
+ * Operations have a type and a state.  Although ints, the values that can
+ * be used are defined in {@link UserBackupManagerService}.  If the type of
+ * an operation is OP_BACKUP, then it represents a task running backups. The
+ * task is provided when registering the operation because it provides a
+ * handle to cancel the backup.
+ */
+public interface OperationStorage {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        OpState.PENDING,
+        OpState.ACKNOWLEDGED,
+        OpState.TIMEOUT
+    })
+    public @interface OpState {
+        // The operation is in progress.
+        int PENDING = 0;
+        // The operation has been acknowledged.
+        int ACKNOWLEDGED = 1;
+        // The operation has timed out.
+        int TIMEOUT = -1;
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        OpType.BACKUP_WAIT,
+        OpType.RESTORE_WAIT,
+        OpType.BACKUP,
+    })
+    public @interface OpType {
+        // Waiting for backup agent to respond during backup operation.
+        int BACKUP_WAIT = 0;
+        // Waiting for backup agent to respond during restore operation.
+        int RESTORE_WAIT = 1;
+        // An entire backup operation spanning multiple packages.
+        int BACKUP = 2;
+    }
+
+    /**
+     * Record an ongoing operation of given type and in the given initial
+     * state. The associated task is used as a callback.
+     *
+     * @param token        an operation token issued by
+     *                     {@link UserBackupManagerService#generateRandomIntegerToken()}
+     * @param initialState the state that the operation starts in
+     * @param task         the {@link BackupRestoreTask} that is expected to
+     *                     remove the operation on completion, and which may
+     *                     be notified if the operation requires cancelling.
+     * @param type         the type of the operation.
+     */
+    void registerOperation(int token, @OpState int initialState,
+            BackupRestoreTask task, @OpType int type);
+
+    /**
+     * See {@link #registerOperation()}.  In addition this method accepts a set
+     * of package names which are associated with the operation.
+     *
+     * @param token        See {@link #registerOperation()}
+     * @param initialState See {@link #registerOperation()}
+     * @param packageNames the package names to associate with the operation.
+     * @param task         See {@link #registerOperation()}
+     * @param type         See {@link #registerOperation()}
+     */
+    void registerOperationForPackages(int token, @OpState int initialState,
+            Set<String> packageNames, BackupRestoreTask task, @OpType int type);
+
+    /**
+     * Remove the operation identified by token.  This is called when the
+     * operation is no longer in progress and should be dropped. Any association
+     * with package names provided in {@link #registerOperation()} is dropped as
+     * well.
+     *
+     * @param token the operation token specified when registering the operation.
+     */
+    void removeOperation(int token);
+
+    /**
+     * Obtain the number of currently registered operations.
+     *
+     * @return the number of currently registered operations.
+     */
+    int numOperations();
+
+    /**
+     * Determine if a backup operation is in progress or not.
+     *
+     * @return true if any operation is registered of type BACKUP and in
+     *         state PENDING.
+     */
+    boolean isBackupOperationInProgress();
+
+    /**
+     * Obtain a set of operation tokens for all pending operations that were
+     * registered with an association to the specified package name.
+     *
+     * @param packageName the name of the package used at registration time
+     *
+     * @return a set of operation tokens associated to package name.
+     */
+    Set<Integer> operationTokensForPackage(String packageName);
+
+    /**
+     * Obtain a set of operation tokens for all pending operations that are
+     * of the specified operation type.
+     *
+     * @param type the type of the operation provided at registration time.
+     *
+     * @return a set of operation tokens for operations of that type.
+     */
+    Set<Integer> operationTokensForOpType(@OpType int type);
+
+    /**
+     * Obtain a set of operation tokens for all pending operations that are
+     * currently in the specified operation state.
+     *
+     * @param state the state of the operation.
+     *
+     * @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 452adb2..f8da035 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -103,17 +103,18 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.Preconditions;
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.fullbackup.FullBackupEntry;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.internal.BackupHandler;
 import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.LifecycleOperationStorage;
 import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
 import com.android.server.backup.internal.PerformInitializeTask;
 import com.android.server.backup.internal.RunInitializeReceiver;
 import com.android.server.backup.internal.SetupObserver;
@@ -127,6 +128,7 @@
 import com.android.server.backup.params.RestoreParams;
 import com.android.server.backup.restore.ActiveRestoreSession;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -287,21 +289,6 @@
     private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
-    // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
-    // pending operations list.
-    public static final int OP_PENDING = 0;
-    private static final int OP_ACKNOWLEDGED = 1;
-    private static final int OP_TIMEOUT = -1;
-
-    // Waiting for backup agent to respond during backup operation.
-    public static final int OP_TYPE_BACKUP_WAIT = 0;
-
-    // Waiting for backup agent to respond during restore operation.
-    public static final int OP_TYPE_RESTORE_WAIT = 1;
-
-    // An entire backup operation spanning multiple packages.
-    public static final int OP_TYPE_BACKUP = 2;
-
     // Time delay for initialization operations that can be delayed so as not to consume too much
     // CPU on bring-up and increase time-to-UI.
     private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -400,30 +387,8 @@
 
     private ActiveRestoreSession mActiveRestoreSession;
 
-    /**
-     * mCurrentOperations contains the list of currently active operations.
-     *
-     * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
-     * An operation wraps a BackupRestoreTask within it.
-     * It's the responsibility of this task to remove the operation from this array.
-     *
-     * A BackupRestore task gets notified of ack/timeout for the operation via
-     * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
-     * on the mCurrentOpLock.
-     * {@link UserBackupManagerService#waitUntilOperationComplete(int)} is
-     * used in various places to 'wait' for notifyAll and detect change of pending state of an
-     * operation. So typically, an operation will be removed from this array by:
-     * - BackupRestoreTask#handleCancel and
-     * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
-     * these places because waitUntilOperationComplete relies on the operation being present to
-     * determine its completion status.
-     *
-     * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
-     * cancel backup tasks.
-     */
-    @GuardedBy("mCurrentOpLock")
-    private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
-    private final Object mCurrentOpLock = new Object();
+    private final LifecycleOperationStorage mOperationStorage;
+
     private final Random mTokenGenerator = new Random();
     private final AtomicInteger mNextToken = new AtomicInteger();
 
@@ -542,12 +507,14 @@
     }
 
     @VisibleForTesting
-    UserBackupManagerService(Context context, PackageManager packageManager) {
+    UserBackupManagerService(Context context, PackageManager packageManager,
+            LifecycleOperationStorage operationStorage) {
         mContext = context;
 
         mUserId = 0;
         mRegisterTransportsRequestedTime = 0;
         mPackageManager = packageManager;
+        mOperationStorage = operationStorage;
 
         mBaseStateDir = null;
         mDataDir = null;
@@ -600,8 +567,10 @@
                 BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
         mAgentTimeoutParameters.start();
 
+        mOperationStorage = new LifecycleOperationStorage(mUserId);
+
         Objects.requireNonNull(userBackupThread, "userBackupThread cannot be null");
-        mBackupHandler = new BackupHandler(this, userBackupThread);
+        mBackupHandler = new BackupHandler(this, mOperationStorage, userBackupThread);
 
         // Set up our bookkeeping
         final ContentResolver resolver = context.getContentResolver();
@@ -756,6 +725,10 @@
         return mTransportManager;
     }
 
+    public OperationStorage getOperationStorage() {
+        return mOperationStorage;
+    }
+
     public boolean isEnabled() {
         return mEnabled;
     }
@@ -838,14 +811,6 @@
         return mActiveRestoreSession;
     }
 
-    public SparseArray<Operation> getCurrentOperations() {
-        return mCurrentOperations;
-    }
-
-    public Object getCurrentOpLock() {
-        return mCurrentOpLock;
-    }
-
     public SparseArray<AdbParams> getAdbBackupRestoreConfirmations() {
         return mAdbBackupRestoreConfirmations;
     }
@@ -1987,18 +1952,12 @@
         }
         final long oldToken = Binder.clearCallingIdentity();
         try {
-            List<Integer> operationsToCancel = new ArrayList<>();
-            synchronized (mCurrentOpLock) {
-                for (int i = 0; i < mCurrentOperations.size(); i++) {
-                    Operation op = mCurrentOperations.valueAt(i);
-                    int token = mCurrentOperations.keyAt(i);
-                    if (op.type == OP_TYPE_BACKUP) {
-                        operationsToCancel.add(token);
-                    }
-                }
-            }
+            Set<Integer> operationsToCancel =
+                    mOperationStorage.operationTokensForOpType(OpType.BACKUP);
+
             for (Integer token : operationsToCancel) {
-                handleCancel(token, true /* cancelAll */);
+                mOperationStorage.cancelOperation(token, /* cancelAll */ true,
+                        operationType -> { /* no callback needed here */ });
             }
             // We don't want the backup jobs to kick in any time soon.
             // Reschedules them to run in the distant future.
@@ -2012,7 +1971,7 @@
     /** Schedule a timeout message for the operation identified by {@code token}. */
     public void prepareOperationTimeout(int token, long interval, BackupRestoreTask callback,
             int operationType) {
-        if (operationType != OP_TYPE_BACKUP_WAIT && operationType != OP_TYPE_RESTORE_WAIT) {
+        if (operationType != OpType.BACKUP_WAIT && operationType != OpType.RESTORE_WAIT) {
             Slog.wtf(
                     TAG,
                     addUserIdToLogMessage(
@@ -2036,19 +1995,17 @@
                                     + callback));
         }
 
-        synchronized (mCurrentOpLock) {
-            mCurrentOperations.put(token, new Operation(OP_PENDING, callback, operationType));
-            Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
-                    token, 0, callback);
-            mBackupHandler.sendMessageDelayed(msg, interval);
-        }
+        mOperationStorage.registerOperation(token, OpState.PENDING, callback, operationType);
+        Message msg = mBackupHandler.obtainMessage(getMessageIdForOperationType(operationType),
+                token, 0, callback);
+        mBackupHandler.sendMessageDelayed(msg, interval);
     }
 
     private int getMessageIdForOperationType(int operationType) {
         switch (operationType) {
-            case OP_TYPE_BACKUP_WAIT:
+            case OpType.BACKUP_WAIT:
                 return MSG_BACKUP_OPERATION_TIMEOUT;
-            case OP_TYPE_RESTORE_WAIT:
+            case OpType.RESTORE_WAIT:
                 return MSG_RESTORE_OPERATION_TIMEOUT;
             default:
                 Slog.wtf(
@@ -2061,162 +2018,28 @@
         }
     }
 
-    /**
-     * Add an operation to the list of currently running operations. Used for cancellation,
-     * completion and timeout callbacks that act on the operation via the {@code token}.
-     */
-    public void putOperation(int token, Operation operation) {
-        if (MORE_DEBUG) {
-            Slog.d(
-                    TAG,
-                    addUserIdToLogMessage(
-                            mUserId,
-                            "Adding operation token="
-                                    + Integer.toHexString(token)
-                                    + ", operation type="
-                                    + operation.type));
-        }
-        synchronized (mCurrentOpLock) {
-            mCurrentOperations.put(token, operation);
-        }
-    }
-
-    /**
-     * Remove an operation from the list of currently running operations. An operation is removed
-     * when it is completed, cancelled, or timed out, and thus no longer running.
-     */
-    public void removeOperation(int token) {
-        if (MORE_DEBUG) {
-            Slog.d(
-                    TAG,
-                    addUserIdToLogMessage(
-                            mUserId, "Removing operation token=" + Integer.toHexString(token)));
-        }
-        synchronized (mCurrentOpLock) {
-            if (mCurrentOperations.get(token) == null) {
-                Slog.w(TAG, addUserIdToLogMessage(mUserId, "Duplicate remove for operation. token="
-                        + Integer.toHexString(token)));
-            }
-            mCurrentOperations.remove(token);
-        }
-    }
-
     /** Block until we received an operation complete message (from the agent or cancellation). */
     public boolean waitUntilOperationComplete(int token) {
-        if (MORE_DEBUG) {
-            Slog.i(TAG, addUserIdToLogMessage(mUserId, "Blocking until operation complete for "
-                    + Integer.toHexString(token)));
-        }
-        int finalState = OP_PENDING;
-        Operation op = null;
-        synchronized (mCurrentOpLock) {
-            while (true) {
-                op = mCurrentOperations.get(token);
-                if (op == null) {
-                    // mysterious disappearance: treat as success with no callback
-                    break;
-                } else {
-                    if (op.state == OP_PENDING) {
-                        try {
-                            mCurrentOpLock.wait();
-                        } catch (InterruptedException e) {
-                        }
-                        // When the wait is notified we loop around and recheck the current state
-                    } else {
-                        if (MORE_DEBUG) {
-                            Slog.d(
-                                    TAG,
-                                    addUserIdToLogMessage(
-                                            mUserId,
-                                            "Unblocked waiting for operation token="
-                                                    + Integer.toHexString(token)));
-                        }
-                        // No longer pending; we're done
-                        finalState = op.state;
-                        break;
-                    }
-                }
-            }
-        }
-
-        removeOperation(token);
-        if (op != null) {
-            mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
-        }
-        if (MORE_DEBUG) {
-            Slog.v(TAG, addUserIdToLogMessage(mUserId, "operation " + Integer.toHexString(token)
-                    + " complete: finalState=" + finalState));
-        }
-        return finalState == OP_ACKNOWLEDGED;
+        return mOperationStorage.waitUntilOperationComplete(token, operationType -> {
+            mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
+        });
     }
 
     /** Cancel the operation associated with {@code token}. */
     public void handleCancel(int token, boolean cancelAll) {
-        // Notify any synchronous waiters
-        Operation op = null;
-        synchronized (mCurrentOpLock) {
-            op = mCurrentOperations.get(token);
-            if (MORE_DEBUG) {
-                if (op == null) {
-                    Slog.w(
-                            TAG,
-                            addUserIdToLogMessage(
-                                    mUserId,
-                                    "Cancel of token "
-                                            + Integer.toHexString(token)
-                                            + " but no op found"));
-                }
+        // Remove all pending timeout messages of types OpType.BACKUP_WAIT and
+        // OpType.RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
+        // doesn't require cancellation.
+        mOperationStorage.cancelOperation(token, cancelAll, operationType -> {
+            if (operationType == OpType.BACKUP_WAIT || operationType == OpType.RESTORE_WAIT) {
+                mBackupHandler.removeMessages(getMessageIdForOperationType(operationType));
             }
-            int state = (op != null) ? op.state : OP_TIMEOUT;
-            if (state == OP_ACKNOWLEDGED) {
-                // The operation finished cleanly, so we have nothing more to do.
-                if (DEBUG) {
-                    Slog.w(TAG, addUserIdToLogMessage(mUserId, "Operation already got an ack."
-                            + "Should have been removed from mCurrentOperations."));
-                }
-                op = null;
-                mCurrentOperations.delete(token);
-            } else if (state == OP_PENDING) {
-                if (DEBUG) {
-                    Slog.v(
-                            TAG,
-                            addUserIdToLogMessage(
-                                    mUserId, "Cancel: token=" + Integer.toHexString(token)));
-                }
-                op.state = OP_TIMEOUT;
-                // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
-                // called after we receive cancel here. We need this op's state there.
-
-                // Remove all pending timeout messages of types OP_TYPE_BACKUP_WAIT and
-                // OP_TYPE_RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
-                // doesn't require cancellation.
-                if (op.type == OP_TYPE_BACKUP_WAIT || op.type == OP_TYPE_RESTORE_WAIT) {
-                    mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
-                }
-            }
-            mCurrentOpLock.notifyAll();
-        }
-
-        // If there's a TimeoutHandler for this event, call it
-        if (op != null && op.callback != null) {
-            if (MORE_DEBUG) {
-                Slog.v(TAG, addUserIdToLogMessage(mUserId, "   Invoking cancel on " + op.callback));
-            }
-            op.callback.handleCancel(cancelAll);
-        }
+        });
     }
 
     /** Returns {@code true} if a backup is currently running, else returns {@code false}. */
     public boolean isBackupOperationInProgress() {
-        synchronized (mCurrentOpLock) {
-            for (int i = 0; i < mCurrentOperations.size(); i++) {
-                Operation op = mCurrentOperations.valueAt(i);
-                if (op.type == OP_TYPE_BACKUP && op.state == OP_PENDING) {
-                    return true;
-                }
-            }
-        }
-        return false;
+        return mOperationStorage.isBackupOperationInProgress();
     }
 
     /** Unbind the backup agent and kill the app if it's a non-system app. */
@@ -2578,6 +2401,7 @@
             String[] pkg = new String[]{entry.packageName};
             mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport(
                     this,
+                    mOperationStorage,
                     /* observer */ null,
                     pkg,
                     /* updateSchedule */ true,
@@ -3107,6 +2931,7 @@
                 CountDownLatch latch = new CountDownLatch(1);
                 Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport(
                         this,
+                        mOperationStorage,
                         /* observer */ null,
                         pkgNames,
                         /* updateSchedule */ false,
@@ -3719,7 +3544,8 @@
                 mTransportManager.getTransportClient(newTransportName, callerLogString);
         if (transportConnection != null) {
             try {
-                IBackupTransport transport = transportConnection.connectOrThrow(callerLogString);
+                BackupTransportClient transport = transportConnection.connectOrThrow(
+                        callerLogString);
                 mCurrentToken = transport.getCurrentRestoreSet();
             } catch (Exception e) {
                 // Oops.  We can't know the current dataset token, so reset and figure it out
@@ -3892,7 +3718,6 @@
      * only be called from the {@link ActivityManager}.
      */
     public void agentDisconnected(String packageName) {
-        // TODO: handle backup being interrupted
         synchronized (mAgentConnectLock) {
             if (Binder.getCallingUid() == Process.SYSTEM_UID) {
                 mConnectedAgent = null;
@@ -3906,6 +3731,20 @@
                                         + Binder.getCallingUid()
                                         + " claiming agent disconnected"));
             }
+            Slog.w(TAG, "agentDisconnected: the backup agent for " + packageName
+                    + " died: cancel current operations");
+
+            // handleCancel() causes the PerformFullTransportBackupTask to go on to
+            // tearDownAgentAndKill: that will unbindBackupAgent in the Activity Manager, so
+            // that the package being backed up doesn't get stuck in restricted mode until the
+            // backup time-out elapses.
+            for (int token : mOperationStorage.operationTokensForPackage(packageName)) {
+                if (MORE_DEBUG) {
+                    Slog.d(TAG, "agentDisconnected: will handleCancel(all) for token:"
+                            + Integer.toHexString(token));
+                }
+                handleCancel(token, true /* cancelAll */);
+            }
             mAgentConnectLock.notifyAll();
         }
     }
@@ -4125,48 +3964,11 @@
      * outstanding asynchronous backup/restore operation.
      */
     public void opComplete(int token, long result) {
-        if (MORE_DEBUG) {
-            Slog.v(
-                    TAG,
-                    addUserIdToLogMessage(
-                            mUserId,
-                            "opComplete: " + Integer.toHexString(token) + " result=" + result));
-        }
-        Operation op = null;
-        synchronized (mCurrentOpLock) {
-            op = mCurrentOperations.get(token);
-            if (op != null) {
-                if (op.state == OP_TIMEOUT) {
-                    // The operation already timed out, and this is a late response.  Tidy up
-                    // and ignore it; we've already dealt with the timeout.
-                    op = null;
-                    mCurrentOperations.delete(token);
-                } else if (op.state == OP_ACKNOWLEDGED) {
-                    if (DEBUG) {
-                        Slog.w(
-                                TAG,
-                                addUserIdToLogMessage(
-                                        mUserId,
-                                        "Received duplicate ack for token="
-                                                + Integer.toHexString(token)));
-                    }
-                    op = null;
-                    mCurrentOperations.remove(token);
-                } else if (op.state == OP_PENDING) {
-                    // Can't delete op from mCurrentOperations. waitUntilOperationComplete can be
-                    // called after we we receive this call.
-                    op.state = OP_ACKNOWLEDGED;
-                }
-            }
-            mCurrentOpLock.notifyAll();
-        }
-
-        // The completion callback, if any, is invoked on the handler
-        if (op != null && op.callback != null) {
-            Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(op.callback, result);
+        mOperationStorage.onOperationComplete(token, result, callback -> {
+            Pair<BackupRestoreTask, Long> callbackAndResult = Pair.create(callback, result);
             Message msg = mBackupHandler.obtainMessage(MSG_OP_COMPLETE, callbackAndResult);
             mBackupHandler.sendMessage(msg);
-        }
+        });
     }
 
     /** Checks if the package is eligible for backup. */
@@ -4371,7 +4173,7 @@
 
         final long oldCallingId = Binder.clearCallingIdentity();
         try {
-            IBackupTransport transport = transportConnection.connectOrThrow(
+            BackupTransportClient transport = transportConnection.connectOrThrow(
                     /* caller */ "BMS.getOperationTypeFromTransport");
             if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
                 return OperationType.MIGRATION;
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 fe5497f..1e1ca95 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -21,7 +21,6 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 
 import android.annotation.UserIdInt;
@@ -39,6 +38,7 @@
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -147,7 +147,7 @@
                         mToken,
                         timeout,
                         mTimeoutMonitor /* in parent class */,
-                        OP_TYPE_BACKUP_WAIT);
+                        OpType.BACKUP_WAIT);
                 mAgent.doFullBackup(
                         mPipe,
                         mQuota,
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
index aaf1f0a..be6ac26 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupObbConnection.java
@@ -18,7 +18,6 @@
 
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 
 import android.app.backup.IBackupManager;
 import android.content.ComponentName;
@@ -33,6 +32,7 @@
 
 import com.android.internal.backup.IObbBackupService;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.utils.FullBackupUtils;
 
@@ -83,7 +83,7 @@
             long fullBackupAgentTimeoutMillis =
                     mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
             backupManagerService.prepareOperationTimeout(
-                    token, fullBackupAgentTimeoutMillis, null, OP_TYPE_BACKUP_WAIT);
+                    token, fullBackupAgentTimeoutMillis, null, OpType.BACKUP_WAIT);
             mService.backupObbs(pkg.packageName, pipes[1], token,
                     backupManagerService.getBackupManagerBinder());
             FullBackupUtils.routeSocketDataToOutput(pipes[0], out);
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 448e086..7ee307e 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -37,6 +37,7 @@
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.KeyValueAdbBackupEngine;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.PasswordUtils;
@@ -67,6 +68,7 @@
 public class PerformAdbBackupTask extends FullBackupTask implements BackupRestoreTask {
 
     private final UserBackupManagerService mUserBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final AtomicBoolean mLatch;
 
     private final ParcelFileDescriptor mOutputFile;
@@ -85,7 +87,8 @@
     private final int mCurrentOpToken;
     private final BackupEligibilityRules mBackupEligibilityRules;
 
-    public PerformAdbBackupTask(UserBackupManagerService backupManagerService,
+    public PerformAdbBackupTask(
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             ParcelFileDescriptor fd, IFullBackupRestoreObserver observer,
             boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
             String curPassword, String encryptPassword, boolean doAllApps, boolean doSystem,
@@ -93,6 +96,7 @@
             BackupEligibilityRules backupEligibilityRules) {
         super(observer);
         mUserBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
         mLatch = latch;
 
@@ -505,6 +509,6 @@
         if (target != null) {
             mUserBackupManagerService.tearDownAgentAndKill(mCurrentTarget.applicationInfo);
         }
-        mUserBackupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 }
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 1c86091..e74a3b9 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -19,9 +19,6 @@
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING;
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
-import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP_WAIT;
 
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
@@ -41,28 +38,33 @@
 import android.util.Log;
 import android.util.Slog;
 
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.EventLogTags;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FullBackupJob;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
 import com.android.server.backup.remote.RemoteCall;
+import com.android.server.backup.transport.BackupTransportClient;
 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 com.android.server.backup.utils.BackupObserverUtils;
 
+import com.google.android.collect.Sets;
+
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
@@ -99,6 +101,7 @@
 public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
     public static PerformFullTransportBackupTask newWithCurrentTransport(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             IFullBackupRestoreObserver observer,
             String[] whichPackages,
             boolean updateSchedule,
@@ -118,6 +121,7 @@
                                 listenerCaller);
         return new PerformFullTransportBackupTask(
                 backupManagerService,
+                operationStorage,
                 transportConnection,
                 observer,
                 whichPackages,
@@ -136,6 +140,7 @@
     private UserBackupManagerService mUserBackupManagerService;
     private final Object mCancelLock = new Object();
 
+    OperationStorage mOperationStorage;
     List<PackageInfo> mPackages;
     PackageInfo mCurrentPackage;
     boolean mUpdateSchedule;
@@ -158,6 +163,7 @@
     private final BackupEligibilityRules mBackupEligibilityRules;
 
     public PerformFullTransportBackupTask(UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             IFullBackupRestoreObserver observer,
             String[] whichPackages, boolean updateSchedule,
@@ -165,7 +171,8 @@
             @Nullable IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
             boolean userInitiated, BackupEligibilityRules backupEligibilityRules) {
         super(observer);
-        this.mUserBackupManagerService = backupManagerService;
+        mUserBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mTransportConnection = transportConnection;
         mUpdateSchedule = updateSchedule;
         mLatch = latch;
@@ -191,8 +198,6 @@
             return;
         }
 
-        registerTask();
-
         for (String pkg : whichPackages) {
             try {
                 PackageManager pm = backupManagerService.getPackageManager();
@@ -258,19 +263,20 @@
         }
 
         mPackages = backupManagerService.filterUserFacingPackages(mPackages);
-    }
 
-    private void registerTask() {
-        synchronized (mUserBackupManagerService.getCurrentOpLock()) {
-            Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
-            mUserBackupManagerService.getCurrentOperations().put(
-                    mCurrentOpToken,
-                    new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
+        Set<String> packageNames = Sets.newHashSet();
+        for (PackageInfo pkgInfo : mPackages) {
+            packageNames.add(pkgInfo.packageName);
         }
+
+        Slog.d(TAG, "backupmanager pftbt token=" + Integer.toHexString(mCurrentOpToken));
+        mOperationStorage.registerOperationForPackages(mCurrentOpToken, OpState.PENDING,
+                packageNames, this, OpType.BACKUP);
     }
 
+    // public, because called from KeyValueBackupTask.finishTask.
     public void unregisterTask() {
-        mUserBackupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 
     @Override
@@ -300,7 +306,7 @@
                 mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
                 try {
                     // If we're running a backup we should be connected to a transport
-                    IBackupTransport transport =
+                    BackupTransportClient transport =
                             mTransportConnection.getConnectedTransport("PFTBT.handleCancel()");
                     transport.cancelFullBackup();
                 } catch (RemoteException | TransportNotAvailableException e) {
@@ -353,7 +359,7 @@
                 return;
             }
 
-            IBackupTransport transport = mTransportConnection.connect("PFTBT.run()");
+            BackupTransportClient transport = mTransportConnection.connect("PFTBT.run()");
             if (transport == null) {
                 Slog.w(TAG, "Transport not present; full data backup not performed");
                 backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -722,7 +728,7 @@
                     mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
             try {
                 mUserBackupManagerService.prepareOperationTimeout(
-                        mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
+                        mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OpType.BACKUP_WAIT);
                 if (MORE_DEBUG) {
                     Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
                 }
@@ -745,7 +751,7 @@
                     Slog.v(TAG, "Got preflight response; size=" + totalSize);
                 }
 
-                IBackupTransport transport =
+                BackupTransportClient transport =
                         mTransportConnection.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
                 result = transport.checkFullBackupSize(totalSize);
                 if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
@@ -777,7 +783,7 @@
             }
             mResult.set(result);
             mLatch.countDown();
-            mUserBackupManagerService.removeOperation(mCurrentOpToken);
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
 
         @Override
@@ -787,7 +793,7 @@
             }
             mResult.set(BackupTransport.AGENT_ERROR);
             mLatch.countDown();
-            mUserBackupManagerService.removeOperation(mCurrentOpToken);
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
 
         @Override
@@ -833,20 +839,17 @@
             mBackupResult = BackupTransport.AGENT_ERROR;
             mQuota = quota;
             mTransportFlags = transportFlags;
-            registerTask();
+            registerTask(target.packageName);
         }
 
-        void registerTask() {
-            synchronized (mUserBackupManagerService.getCurrentOpLock()) {
-                mUserBackupManagerService.getCurrentOperations().put(
-                        mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP_WAIT));
-            }
+        void registerTask(String packageName) {
+            Set<String> packages = Sets.newHashSet(packageName);
+            mOperationStorage.registerOperationForPackages(mCurrentOpToken, OpState.PENDING,
+                    packages, this, OpType.BACKUP_WAIT);
         }
 
         void unregisterTask() {
-            synchronized (mUserBackupManagerService.getCurrentOpLock()) {
-                mUserBackupManagerService.getCurrentOperations().remove(mCurrentOpToken);
-            }
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
 
         @Override
@@ -956,7 +959,7 @@
             mPreflightLatch.countDown();
             mBackupLatch.countDown();
             // We are done with this operation.
-            mUserBackupManagerService.removeOperation(mCurrentOpToken);
+            mOperationStorage.removeOperation(mCurrentOpToken);
         }
     }
 }
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 3b3bf8c6..03796ea 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -31,11 +31,11 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.EventLogTags;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.DataChangedJournal;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.PerformAdbBackupTask;
@@ -51,6 +51,7 @@
 import com.android.server.backup.params.RestoreParams;
 import com.android.server.backup.restore.PerformAdbRestoreTask;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 
 import java.util.ArrayList;
@@ -84,6 +85,7 @@
     public static final int MSG_STOP = 22;
 
     private final UserBackupManagerService backupManagerService;
+    private final OperationStorage mOperationStorage;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
 
     private final HandlerThread mBackupThread;
@@ -92,10 +94,12 @@
     volatile boolean mIsStopping = false;
 
     public BackupHandler(
-            UserBackupManagerService backupManagerService, HandlerThread backupThread) {
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
+            HandlerThread backupThread) {
         super(backupThread.getLooper());
         mBackupThread = backupThread;
         this.backupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mAgentTimeoutParameters = Objects.requireNonNull(
                 backupManagerService.getAgentTimeoutParameters(),
                 "Timeout parameters cannot be null");
@@ -149,7 +153,7 @@
                 String callerLogString = "BH/MSG_RUN_BACKUP";
                 TransportConnection transportConnection =
                         transportManager.getCurrentTransportClient(callerLogString);
-                IBackupTransport transport =
+                BackupTransportClient transport =
                         transportConnection != null
                                 ? transportConnection.connect(callerLogString)
                                 : null;
@@ -215,6 +219,7 @@
                                                         caller);
                         KeyValueBackupTask.start(
                                 backupManagerService,
+                                mOperationStorage,
                                 transportConnection,
                                 transport.transportDirName(),
                                 queue,
@@ -278,8 +283,8 @@
                 // TODO: refactor full backup to be a looper-based state machine
                 // similar to normal backup/restore.
                 AdbBackupParams params = (AdbBackupParams) msg.obj;
-                PerformAdbBackupTask task = new PerformAdbBackupTask(backupManagerService,
-                        params.fd,
+                PerformAdbBackupTask task = new PerformAdbBackupTask(
+                        backupManagerService, mOperationStorage, params.fd,
                         params.observer, params.includeApks, params.includeObbs,
                         params.includeShared, params.doWidgets, params.curPassword,
                         params.encryptPassword, params.allApps, params.includeSystem,
@@ -296,6 +301,7 @@
                 PerformUnifiedRestoreTask task =
                         new PerformUnifiedRestoreTask(
                                 backupManagerService,
+                                mOperationStorage,
                                 params.mTransportConnection,
                                 params.observer,
                                 params.monitor,
@@ -332,7 +338,7 @@
                 // similar to normal backup/restore.
                 AdbRestoreParams params = (AdbRestoreParams) msg.obj;
                 PerformAdbRestoreTask task = new PerformAdbRestoreTask(backupManagerService,
-                        params.fd,
+                        mOperationStorage, params.fd,
                         params.curPassword, params.encryptPassword,
                         params.observer, params.latch);
                 (new Thread(task, "adb-restore")).start();
@@ -364,7 +370,7 @@
                 RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
                 String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS";
                 try {
-                    IBackupTransport transport =
+                    BackupTransportClient transport =
                             params.mTransportConnection.connectOrThrow(callerLogString);
                     sets = transport.getAvailableRestoreSets();
                     // cache the result in the active session
@@ -459,6 +465,7 @@
 
                 KeyValueBackupTask.start(
                         backupManagerService,
+                        mOperationStorage,
                         params.mTransportConnection,
                         params.dirName,
                         params.kvPackages,
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
new file mode 100644
index 0000000..6908c60
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -0,0 +1,356 @@
+/*
+ * 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.backup.internal;
+
+import static com.android.server.backup.BackupManagerService.DEBUG;
+import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
+
+import android.annotation.UserIdInt;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage;
+
+import com.google.android.collect.Sets;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+/**
+ * LifecycleOperationStorage is responsible for maintaining a set of currently
+ * active operations.  Each operation has a type and state, and a callback that
+ * can receive events upon operation completion or cancellation.  It may also
+ * be associated with one or more package names.
+ *
+ * An operation wraps a {@link BackupRestoreTask} within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * If type of operation is {@code OP_TYPE_WAIT}, it is waiting for an ACK or
+ * timeout.
+ *
+ * A BackupRestore task gets notified of AVK/timeout for the operation via
+ * {@link BackupRestoreTask#handleCancel()},
+ * {@link BackupRestoreTask#operationComplete()} and {@code notifyAll} called
+ * on the {@code mCurrentOpLock}.
+ *
+ * {@link LifecycleOperationStorage#waitUntilOperationComplete(int)} is used in
+ * various places to 'wait' for notifyAll and detect change of pending state of
+ * an operation. So typically, an operation will be removed from this array by:
+ * - {@link BackupRestoreTask#handleCancel()} and
+ * - {@link BackupRestoreTask#operationComplete()} OR
+ *   {@link BackupRestoreTask#waitUntilOperationComplete()}.
+ * Do not remove at both these places because {@code waitUntilOperationComplete}
+ * relies on the operation being present to determine its completion status.
+ *
+ * If type of operation is {@code OP_BACKUP}, it is a task running backups. It
+ * provides a handle to cancel backup tasks.
+ */
+public class LifecycleOperationStorage implements OperationStorage {
+    private static final String TAG = "LifecycleOperationStorage";
+
+    private final int mUserId;
+
+    private final Object mOperationsLock = new Object();
+
+    // Bookkeeping of in-flight operations. The operation token is the index of
+    // the entry in the pending operations list.
+    @GuardedBy("mOperationsLock")
+    private final SparseArray<Operation> mOperations = new SparseArray<>();
+
+    // Association from package name to one or more operations relating to that
+    // package.
+    @GuardedBy("mOperationsLock")
+    private final Map<String, Set<Integer>> mOpTokensByPackage = new HashMap<>();
+
+    public LifecycleOperationStorage(@UserIdInt int userId) {
+        this.mUserId = userId;
+    }
+
+    /** See {@link OperationStorage#registerOperation()} */
+    @Override
+    public void registerOperation(int token, @OpState int initialState,
+            BackupRestoreTask task, @OpType int type) {
+        registerOperationForPackages(token, initialState, Sets.newHashSet(), task, type);
+    }
+
+    /** See {@link OperationStorage#registerOperationForPackages()} */
+    @Override
+    public void registerOperationForPackages(int token, @OpState int initialState,
+            Set<String> packageNames, BackupRestoreTask task, @OpType int type) {
+        synchronized (mOperationsLock) {
+            mOperations.put(token, new Operation(initialState, task, type));
+            for (String packageName : packageNames) {
+                Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+                if (tokens == null) {
+                    tokens = new HashSet<Integer>();
+                }
+                tokens.add(token);
+                mOpTokensByPackage.put(packageName, tokens);
+            }
+        }
+    }
+
+    /** See {@link OperationStorage#removeOperation()} */
+    @Override
+    public void removeOperation(int token) {
+        synchronized (mOperationsLock) {
+            mOperations.remove(token);
+            Set<String> packagesWithTokens = mOpTokensByPackage.keySet();
+            for (String packageName : packagesWithTokens) {
+                Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+                if (tokens == null) {
+                    continue;
+                }
+                tokens.remove(token);
+                mOpTokensByPackage.put(packageName, tokens);
+            }
+        }
+    }
+
+    /** See {@link OperationStorage#numOperations()}. */
+    @Override
+    public int numOperations() {
+        synchronized (mOperationsLock) {
+            return mOperations.size();
+        }
+    }
+
+    /** See {@link OperationStorage#isBackupOperationInProgress()}. */
+    @Override
+    public boolean isBackupOperationInProgress() {
+        synchronized (mOperationsLock) {
+            for (int i = 0; i < mOperations.size(); i++) {
+                Operation op = mOperations.valueAt(i);
+                if (op.type == OpType.BACKUP && op.state == OpState.PENDING) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /** See {@link OperationStorage#operationTokensForPackage()} */
+    @Override
+    public Set<Integer> operationTokensForPackage(String packageName) {
+        synchronized (mOperationsLock) {
+            final Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+            Set<Integer> result = Sets.newHashSet();
+            if (tokens != null) {
+                result.addAll(tokens);
+            }
+            return result;
+        }
+    }
+
+    /** See {@link OperationStorage#operationTokensForOpType()} */
+    @Override
+    public Set<Integer> operationTokensForOpType(@OpType int type) {
+        Set<Integer> tokens = Sets.newHashSet();
+        synchronized (mOperationsLock) {
+            for (int i = 0; i < mOperations.size(); i++) {
+                final Operation op = mOperations.valueAt(i);
+                final int token = mOperations.keyAt(i);
+                if (op.type == type) {
+                    tokens.add(token);
+                }
+            }
+            return tokens;
+        }
+    }
+
+    /** See {@link OperationStorage#operationTokensForOpState()} */
+    @Override
+    public Set<Integer> operationTokensForOpState(@OpState int state) {
+        Set<Integer> tokens = Sets.newHashSet();
+        synchronized (mOperationsLock) {
+            for (int i = 0; i < mOperations.size(); i++) {
+                final Operation op = mOperations.valueAt(i);
+                final int token = mOperations.keyAt(i);
+                if (op.state == state) {
+                    tokens.add(token);
+                }
+            }
+            return tokens;
+        }
+    }
+
+    /**
+     * A blocking function that blocks the caller until the operation identified
+     * by {@code token} is complete - either via a message from the backup,
+     * agent or through cancellation.
+     *
+     * @param token the operation token specified when registering the operation
+     * @param callback a lambda which is invoked once only when the operation
+     *                 completes - ie. if this method is called twice for the
+     *                 same token, the lambda is not invoked the second time.
+     * @return true if the operation was ACKed prior to or during this call.
+     */
+    public boolean waitUntilOperationComplete(int token, IntConsumer callback) {
+        if (MORE_DEBUG) {
+            Slog.i(TAG, "[UserID:" + mUserId + "] Blocking until operation complete for "
+                    + Integer.toHexString(token));
+        }
+        @OpState int finalState = OpState.PENDING;
+        Operation op = null;
+        synchronized (mOperationsLock) {
+            while (true) {
+                op = mOperations.get(token);
+                if (op == null) {
+                    // mysterious disappearance: treat as success with no callback
+                    break;
+                } else {
+                    if (op.state == OpState.PENDING) {
+                        try {
+                            mOperationsLock.wait();
+                        } catch (InterruptedException e) {
+                            Slog.w(TAG, "Waiting on mOperationsLock: ", e);
+                        }
+                        // When the wait is notified we loop around and recheck the current state
+                    } else {
+                        if (MORE_DEBUG) {
+                            Slog.d(TAG, "[UserID:" + mUserId
+                                    + "] Unblocked waiting for operation token="
+                                    + Integer.toHexString(token));
+                        }
+                        // No longer pending; we're done
+                        finalState = op.state;
+                        break;
+                    }
+                }
+            }
+        }
+
+        removeOperation(token);
+        if (op != null) {
+            callback.accept(op.type);
+        }
+        if (MORE_DEBUG) {
+            Slog.v(TAG, "[UserID:" + mUserId + "] operation " + Integer.toHexString(token)
+                    + " complete: finalState=" + finalState);
+        }
+        return finalState == OpState.ACKNOWLEDGED;
+    }
+
+    /**
+     * Signals that an ongoing operation is complete: after a currently-active
+     * backup agent has notified us that it has completed the outstanding
+     * asynchronous backup/restore operation identified by the supplied
+     * {@code} token.
+     *
+     * @param token the operation token specified when registering the operation
+     * @param result a result code or error code for the completed operation
+     * @param callback a lambda that is invoked if the completion moves the
+     *                 operation from PENDING to ACKNOWLEDGED state.
+     */
+    public void onOperationComplete(int token, long result, Consumer<BackupRestoreTask> callback) {
+        if (MORE_DEBUG) {
+            Slog.v(TAG, "[UserID:" + mUserId + "] onOperationComplete: "
+                    + Integer.toHexString(token) + " result=" + result);
+        }
+        Operation op = null;
+        synchronized (mOperationsLock) {
+            op = mOperations.get(token);
+            if (op != null) {
+                if (op.state == OpState.TIMEOUT) {
+                    // The operation already timed out, and this is a late response.  Tidy up
+                    // and ignore it; we've already dealt with the timeout.
+                    op = null;
+                    mOperations.remove(token);
+                } else if (op.state == OpState.ACKNOWLEDGED) {
+                    if (DEBUG) {
+                        Slog.w(TAG, "[UserID:" + mUserId + "] Received duplicate ack for token="
+                                + Integer.toHexString(token));
+                    }
+                    op = null;
+                    mOperations.remove(token);
+                } else if (op.state == OpState.PENDING) {
+                    // Can't delete op from mOperations. waitUntilOperationComplete can be
+                    // called after we we receive this call.
+                    op.state = OpState.ACKNOWLEDGED;
+                }
+            }
+            mOperationsLock.notifyAll();
+        }
+
+        // Invoke the operation's completion callback, if there is one.
+        if (op != null && op.callback != null) {
+            callback.accept(op.callback);
+        }
+    }
+
+    /**
+     * Cancel the operation associated with {@code token}.  Cancellation may be
+     * propagated to the operation's callback (a {@link BackupRestoreTask}) if
+     * the operation has one, and the cancellation is due to the operation
+     * timing out.
+     *
+     * @param token the operation token specified when registering the operation
+     * @param cancelAll this is passed on when propagating the cancellation
+     * @param operationTimedOutCallback a lambda that is invoked with the
+     *                                  operation type where the operation is
+     *                                  cancelled due to timeout, allowing the
+     *                                  caller to do type-specific clean-ups.
+     */
+    public void cancelOperation(
+            int token, boolean cancelAll, IntConsumer operationTimedOutCallback) {
+        // Notify any synchronous waiters
+        Operation op = null;
+        synchronized (mOperationsLock) {
+            op = mOperations.get(token);
+            if (MORE_DEBUG) {
+                if (op == null) {
+                    Slog.w(TAG, "[UserID:" + mUserId + "] Cancel of token "
+                            + Integer.toHexString(token) + " but no op found");
+                }
+            }
+            int state = (op != null) ? op.state : OpState.TIMEOUT;
+            if (state == OpState.ACKNOWLEDGED) {
+                // The operation finished cleanly, so we have nothing more to do.
+                if (DEBUG) {
+                    Slog.w(TAG, "[UserID:" + mUserId + "] Operation already got an ack."
+                            + "Should have been removed from mCurrentOperations.");
+                }
+                op = null;
+                mOperations.delete(token);
+            } else if (state == OpState.PENDING) {
+                if (DEBUG) {
+                    Slog.v(TAG, "[UserID:" + mUserId + "] Cancel: token="
+                            + Integer.toHexString(token));
+                }
+                op.state = OpState.TIMEOUT;
+                // Can't delete op from mOperations here. waitUntilOperationComplete may be
+                // called after we receive cancel here. We need this op's state there.
+                operationTimedOutCallback.accept(op.type);
+            }
+            mOperationsLock.notifyAll();
+        }
+
+        // If there's a TimeoutHandler for this event, call it
+        if (op != null && op.callback != null) {
+            if (MORE_DEBUG) {
+                Slog.v(TAG, "[UserID:" + mUserId + "   Invoking cancel on " + op.callback);
+            }
+            op.callback.handleCancel(cancelAll);
+        }
+    }
+};
diff --git a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
index 80bd604..de0177c 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
@@ -21,9 +21,9 @@
 import android.content.pm.PackageInfo;
 import android.util.Slog;
 
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 
 import java.io.File;
@@ -47,7 +47,7 @@
 
     public void run() {
         String callerLogString = "PerformClearTask.run()";
-        IBackupTransport transport = null;
+        BackupTransportClient transport = null;
         try {
             // Clear the on-device backup state to ensure a full backup next time
             String transportDirName =
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
index 7636ef6..888f49d 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -28,10 +28,10 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.EventLogTags;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 
 import java.io.File;
@@ -128,7 +128,8 @@
                 EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
                 long startRealtime = SystemClock.elapsedRealtime();
 
-                IBackupTransport transport = transportConnection.connectOrThrow(callerLogString);
+                BackupTransportClient transport = transportConnection.connectOrThrow(
+                        callerLogString);
                 int status = transport.initializeDevice();
                 if (status != BackupTransport.TRANSPORT_OK) {
                     Slog.e(TAG, "Transport error in initializeDevice()");
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 bdb2e6f..16aa4eb 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -23,8 +23,6 @@
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
 
 import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
-import static com.android.server.backup.UserBackupManagerService.OP_PENDING;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_BACKUP;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -51,20 +49,22 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.Preconditions;
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.DataChangedJournal;
 import com.android.server.backup.KeyValueBackupJob;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.internal.OnTaskFinishedListener;
-import com.android.server.backup.internal.Operation;
 import com.android.server.backup.remote.RemoteCall;
 import com.android.server.backup.remote.RemoteCallable;
 import com.android.server.backup.remote.RemoteResult;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -111,7 +111,7 @@
  * </ul>
  *
  * If there is no PackageManager (PM) pseudo-package state file in the state directory, the
- * specified transport will be initialized with {@link IBackupTransport#initializeDevice()}.
+ * specified transport will be initialized with {@link BackupTransportClient#initializeDevice()}.
  *
  * <p>The PM pseudo-package is the first package to be backed-up and sent to the transport in case
  * of incremental choice. If non-incremental, PM will only be backed-up if specified in the queue,
@@ -141,8 +141,8 @@
  *       </ul>
  *   <li>Unbind the agent.
  *   <li>Assuming agent response, send the staged data that the agent wrote to disk to the transport
- *       via {@link IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)}.
- *   <li>Call {@link IBackupTransport#finishBackup()} if previous call was successful.
+ *       via {@link BackupTransportClient#performBackup(PackageInfo, ParcelFileDescriptor, int)}.
+ *   <li>Call {@link BackupTransportClient#finishBackup()} if previous call was successful.
  *   <li>Save the new state in the state file. During the agent call it was being written to
  *       &lt;state file&gt;.new, here we rename it and replace the old one.
  *   <li>Delete the stage file.
@@ -155,7 +155,7 @@
  *   <li>Delete the {@link DataChangedJournal} provided. Note that this should not be the current
  *       journal.
  *   <li>Set {@link UserBackupManagerService} current token as {@link
- *       IBackupTransport#getCurrentRestoreSet()}, if applicable.
+ *       BackupTransportClient#getCurrentRestoreSet()}, if applicable.
  *   <li>Add the transport to the list of transports pending initialization ({@link
  *       UserBackupManagerService#getPendingInits()}) and kick-off initialization if the transport
  *       ever returned {@link BackupTransport#TRANSPORT_NOT_INITIALIZED}.
@@ -194,7 +194,7 @@
      * @param backupManagerService The {@link UserBackupManagerService} instance.
      * @param transportConnection The {@link TransportConnection} that contains the transport used
      *     for the operation.
-     * @param transportDirName The value of {@link IBackupTransport#transportDirName()} for the
+     * @param transportDirName The value of {@link BackupTransportClient#transportDirName()} for the
      *     transport whose {@link TransportConnection} was provided above.
      * @param queue The list of package names that will be backed-up.
      * @param dataChangedJournal The old data-changed journal file that will be deleted when the
@@ -211,6 +211,7 @@
      */
     public static KeyValueBackupTask start(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             String transportDirName,
             List<String> queue,
@@ -227,6 +228,7 @@
         KeyValueBackupTask task =
                 new KeyValueBackupTask(
                         backupManagerService,
+                        operationStorage,
                         transportConnection,
                         transportDirName,
                         queue,
@@ -244,6 +246,7 @@
     }
 
     private final UserBackupManagerService mBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final PackageManager mPackageManager;
     private final TransportConnection mTransportConnection;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
@@ -302,6 +305,7 @@
     @VisibleForTesting
     public KeyValueBackupTask(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             String transportDirName,
             List<String> queue,
@@ -313,6 +317,7 @@
             boolean nonIncremental,
             BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mPackageManager = backupManagerService.getPackageManager();
         mTransportConnection = transportConnection;
         mOriginalQueue = queue;
@@ -338,12 +343,11 @@
     }
 
     private void registerTask() {
-        mBackupManagerService.putOperation(
-                mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP));
+        mOperationStorage.registerOperation(mCurrentOpToken, OpState.PENDING, this, OpType.BACKUP);
     }
 
     private void unregisterTask() {
-        mBackupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 
     @Override
@@ -417,7 +421,7 @@
 
         boolean noDataPackageEncountered = false;
         try {
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow("KVBT.informTransportOfEmptyBackups()");
 
             for (String packageName : succeedingPackages) {
@@ -467,8 +471,8 @@
     }
 
     /** Send the "no data changed" message to a transport for a specific package */
-    private void sendNoDataChangedTo(IBackupTransport transport, PackageInfo packageInfo, int flags)
-            throws RemoteException {
+    private void sendNoDataChangedTo(BackupTransportClient transport, PackageInfo packageInfo,
+            int flags) throws RemoteException {
         ParcelFileDescriptor pfd;
         try {
             pfd = ParcelFileDescriptor.open(mBlankStateFile, MODE_READ_ONLY | MODE_CREATE);
@@ -608,7 +612,8 @@
         mReporter.onQueueReady(mQueue);
         File pmState = new File(mStateDirectory, PM_PACKAGE);
         try {
-            IBackupTransport transport = mTransportConnection.connectOrThrow("KVBT.startTask()");
+            BackupTransportClient transport = mTransportConnection.connectOrThrow(
+                    "KVBT.startTask()");
             String transportName = transport.name();
             if (transportName.contains("EncryptedLocalTransport")) {
                 // Temporary code for EiTF POC. Only supports non-incremental backups.
@@ -638,6 +643,7 @@
     private PerformFullTransportBackupTask createFullBackupTask(List<String> packages) {
         return new PerformFullTransportBackupTask(
                 mBackupManagerService,
+                mOperationStorage,
                 mTransportConnection,
                 /* fullBackupRestoreObserver */ null,
                 packages.toArray(new String[packages.size()]),
@@ -764,7 +770,8 @@
         long currentToken = mBackupManagerService.getCurrentToken();
         if (mHasDataToBackup && (status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
             try {
-                IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString);
+                BackupTransportClient transport = mTransportConnection.connectOrThrow(
+                        callerLogString);
                 transportName = transport.name();
                 mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
                 mBackupManagerService.writeRestoreTokens();
@@ -835,7 +842,7 @@
 
     @GuardedBy("mQueueLock")
     private void triggerTransportInitializationLocked() throws Exception {
-        IBackupTransport transport =
+        BackupTransportClient transport =
                 mTransportConnection.connectOrThrow("KVBT.triggerTransportInitializationLocked");
         mBackupManagerService.getPendingInits().add(transport.name());
         deletePmStateFile();
@@ -919,7 +926,7 @@
                 }
             }
 
-            IBackupTransport transport = mTransportConnection.connectOrThrow(
+            BackupTransportClient transport = mTransportConnection.connectOrThrow(
                     "KVBT.extractAgentData()");
             long quota = transport.getBackupQuota(packageName, /* isFullBackup */ false);
             int transportFlags = transport.getTransportFlags();
@@ -1078,7 +1085,7 @@
         int status;
         try (ParcelFileDescriptor backupData =
                 ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow("KVBT.transportPerformBackup()");
             mReporter.onTransportPerformBackup(packageName);
             int flags = getPerformBackupFlags(mUserInitiated, nonIncremental);
@@ -1131,7 +1138,7 @@
     private void agentDoQuotaExceeded(@Nullable IBackupAgent agent, String packageName, long size) {
         if (agent != null) {
             try {
-                IBackupTransport transport =
+                BackupTransportClient transport =
                         mTransportConnection.connectOrThrow("KVBT.agentDoQuotaExceeded()");
                 long quota = transport.getBackupQuota(packageName, false);
                 remoteCall(
@@ -1227,7 +1234,7 @@
         mReporter.onRevertTask();
         long delay;
         try {
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow("KVBT.revertTask()");
             delay = transport.requestBackupTime();
         } catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
index 376b618..cfc0f20 100644
--- a/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
+++ b/services/backup/java/com/android/server/backup/restore/AdbRestoreFinishedLatch.java
@@ -23,6 +23,7 @@
 
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 
 import java.util.Objects;
@@ -36,13 +37,16 @@
 
     private static final String TAG = "AdbRestoreFinishedLatch";
     private UserBackupManagerService backupManagerService;
+    private final OperationStorage mOperationStorage;
     final CountDownLatch mLatch;
     private final int mCurrentOpToken;
     private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
 
     public AdbRestoreFinishedLatch(UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             int currentOpToken) {
         this.backupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mLatch = new CountDownLatch(1);
         mCurrentOpToken = currentOpToken;
         mAgentTimeoutParameters = Objects.requireNonNull(
@@ -72,7 +76,7 @@
             Slog.w(TAG, "adb onRestoreFinished() complete");
         }
         mLatch.countDown();
-        backupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 
     @Override
@@ -81,6 +85,6 @@
             Slog.w(TAG, "adb onRestoreFinished() timed out");
         }
         mLatch.countDown();
-        backupManagerService.removeOperation(mCurrentOpToken);
+        mOperationStorage.removeOperation(mCurrentOpToken);
     }
 }
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 5718bdf..76df8b9 100644
--- a/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
+++ b/services/backup/java/com/android/server/backup/restore/FullRestoreEngine.java
@@ -21,7 +21,6 @@
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_MANIFEST_FILENAME;
 import static com.android.server.backup.UserBackupManagerService.BACKUP_METADATA_FILENAME;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
 import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERATION_TIMEOUT;
 
@@ -48,6 +47,8 @@
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FileMetadata;
 import com.android.server.backup.KeyValueAdbRestoreEngine;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -71,6 +72,7 @@
 public class FullRestoreEngine extends RestoreEngine {
 
     private final UserBackupManagerService mBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final int mUserId;
 
     // Task in charge of monitoring timeouts
@@ -133,12 +135,14 @@
     private boolean mPipesClosed;
     private final BackupEligibilityRules mBackupEligibilityRules;
 
-    public FullRestoreEngine(UserBackupManagerService backupManagerService,
+    public FullRestoreEngine(
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             BackupRestoreTask monitorTask, IFullBackupRestoreObserver observer,
             IBackupManagerMonitor monitor, PackageInfo onlyPackage, boolean allowApks,
             int ephemeralOpToken, boolean isAdbRestore,
             BackupEligibilityRules backupEligibilityRules) {
         mBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mEphemeralOpToken = ephemeralOpToken;
         mMonitorTask = monitorTask;
         mObserver = observer;
@@ -409,7 +413,7 @@
                             mBackupManagerService.prepareOperationTimeout(token,
                                     timeout,
                                     mMonitorTask,
-                                    OP_TYPE_RESTORE_WAIT);
+                                    OpType.RESTORE_WAIT);
 
                             if (FullBackup.OBB_TREE_TOKEN.equals(info.domain)) {
                                 if (DEBUG) {
@@ -603,9 +607,9 @@
                     long fullBackupAgentTimeoutMillis =
                             mAgentTimeoutParameters.getFullBackupAgentTimeoutMillis();
                     final AdbRestoreFinishedLatch latch = new AdbRestoreFinishedLatch(
-                            mBackupManagerService, token);
+                            mBackupManagerService, mOperationStorage, token);
                     mBackupManagerService.prepareOperationTimeout(
-                            token, fullBackupAgentTimeoutMillis, latch, OP_TYPE_RESTORE_WAIT);
+                            token, fullBackupAgentTimeoutMillis, latch, OpType.RESTORE_WAIT);
                     if (mTargetApp.processName.equals("system")) {
                         if (MORE_DEBUG) {
                             Slog.d(TAG, "system agent - restoreFinished on thread");
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 e03150e..22af19e 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.fullbackup.FullBackupObbConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
@@ -60,6 +61,7 @@
 public class PerformAdbRestoreTask implements Runnable {
 
     private final UserBackupManagerService mBackupManagerService;
+    private final OperationStorage mOperationStorage;
     private final ParcelFileDescriptor mInputFile;
     private final String mCurrentPassword;
     private final String mDecryptPassword;
@@ -68,10 +70,12 @@
 
     private IFullBackupRestoreObserver mObserver;
 
-    public PerformAdbRestoreTask(UserBackupManagerService backupManagerService,
+    public PerformAdbRestoreTask(
+            UserBackupManagerService backupManagerService, OperationStorage operationStorage,
             ParcelFileDescriptor fd, String curPassword, String decryptPassword,
             IFullBackupRestoreObserver observer, AtomicBoolean latch) {
         this.mBackupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mInputFile = fd;
         mCurrentPassword = curPassword;
         mDecryptPassword = decryptPassword;
@@ -109,9 +113,9 @@
                     mBackupManagerService.getPackageManager(),
                     LocalServices.getService(PackageManagerInternal.class),
                     mBackupManagerService.getUserId(), BackupManager.OperationType.ADB_BACKUP);
-            FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService, null,
-                    mObserver, null, null, true, 0 /*unused*/, true,
-                    eligibilityRules);
+            FullRestoreEngine mEngine = new FullRestoreEngine(mBackupManagerService,
+                    mOperationStorage, null, mObserver, null, null,
+                    true, 0 /*unused*/, true, eligibilityRules);
             FullRestoreEngineThread mEngineThread = new FullRestoreEngineThread(mEngine,
                     tarInputStream);
             mEngineThread.run();
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 8c786d5..b48367d 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -20,7 +20,6 @@
 import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 import static com.android.server.backup.UserBackupManagerService.KEY_WIDGET_STATE;
-import static com.android.server.backup.UserBackupManagerService.OP_TYPE_RESTORE_WAIT;
 import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
 import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
 import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTORE_STEP;
@@ -54,18 +53,20 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 import com.android.server.backup.BackupAgentTimeoutParameters;
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.BackupUtils;
+import com.android.server.backup.OperationStorage;
+import com.android.server.backup.OperationStorage.OpType;
 import com.android.server.backup.PackageManagerBackupAgent;
 import com.android.server.backup.PackageManagerBackupAgent.Metadata;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
 import com.android.server.backup.utils.BackupManagerMonitorUtils;
@@ -84,6 +85,7 @@
 public class PerformUnifiedRestoreTask implements BackupRestoreTask {
 
     private UserBackupManagerService backupManagerService;
+    private final OperationStorage mOperationStorage;
     private final int mUserId;
     private final TransportManager mTransportManager;
     // Transport client we're working with to do the restore
@@ -169,6 +171,7 @@
     PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
         mListener = null;
         mAgentTimeoutParameters = null;
+        mOperationStorage = null;
         mTransportConnection = null;
         mTransportManager = null;
         mEphemeralOpToken = 0;
@@ -181,6 +184,7 @@
     // about releasing it.
     public PerformUnifiedRestoreTask(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
@@ -192,6 +196,7 @@
             OnTaskFinishedListener listener,
             BackupEligibilityRules backupEligibilityRules) {
         this.backupManagerService = backupManagerService;
+        mOperationStorage = operationStorage;
         mUserId = backupManagerService.getUserId();
         mTransportManager = backupManagerService.getTransportManager();
         mEphemeralOpToken = backupManagerService.generateRandomIntegerToken();
@@ -397,7 +402,7 @@
 
             PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]);
 
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow("PerformUnifiedRestoreTask.startRestore()");
 
             mStatus = transport.startRestore(mToken, packages);
@@ -495,7 +500,7 @@
     private void dispatchNextRestore() {
         UnifiedRestoreState nextState = UnifiedRestoreState.FINAL;
         try {
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow(
                             "PerformUnifiedRestoreTask.dispatchNextRestore()");
             mRestoreDescription = transport.nextRestorePackage();
@@ -709,7 +714,7 @@
         boolean startedAgentRestore = false;
 
         try {
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow(
                             "PerformUnifiedRestoreTask.initiateOneRestore()");
 
@@ -767,7 +772,7 @@
             long restoreAgentTimeoutMillis = mAgentTimeoutParameters.getRestoreAgentTimeoutMillis(
                     app.applicationInfo.uid);
             backupManagerService.prepareOperationTimeout(
-                    mEphemeralOpToken, restoreAgentTimeoutMillis, this, OP_TYPE_RESTORE_WAIT);
+                    mEphemeralOpToken, restoreAgentTimeoutMillis, this, OpType.RESTORE_WAIT);
             startedAgentRestore = true;
             mAgent.doRestoreWithExcludedKeys(mBackupData, appVersionCode, mNewState,
                     mEphemeralOpToken, backupManagerService.getBackupManagerBinder(),
@@ -877,7 +882,7 @@
             backupManagerService
                     .prepareOperationTimeout(mEphemeralOpToken,
                             restoreAgentFinishedTimeoutMillis, this,
-                            OP_TYPE_RESTORE_WAIT);
+                            OpType.RESTORE_WAIT);
             mAgent.doRestoreFinished(mEphemeralOpToken,
                     backupManagerService.getBackupManagerBinder());
             // If we get this far, the callback or timeout will schedule the
@@ -921,7 +926,7 @@
             EventLog.writeEvent(EventLogTags.FULL_RESTORE_PACKAGE,
                     mCurrentPackage.packageName);
 
-            mEngine = new FullRestoreEngine(backupManagerService, this, null,
+            mEngine = new FullRestoreEngine(backupManagerService, mOperationStorage, this, null,
                     mMonitor, mCurrentPackage, false, mEphemeralOpToken, false,
                     mBackupEligibilityRules);
             mEngineThread = new FullRestoreEngineThread(mEngine, mEnginePipes[0]);
@@ -940,7 +945,8 @@
 
             String callerLogString = "PerformUnifiedRestoreTask$StreamFeederThread.run()";
             try {
-                IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString);
+                BackupTransportClient transport = mTransportConnection.connectOrThrow(
+                        callerLogString);
                 while (status == BackupTransport.TRANSPORT_OK) {
                     // have the transport write some of the restoring data to us
                     int result = transport.getNextFullRestoreDataChunk(tWriteEnd);
@@ -1032,7 +1038,7 @@
                     // Something went wrong somewhere.  Whether it was at the transport
                     // level is immaterial; we need to tell the transport to bail
                     try {
-                        IBackupTransport transport =
+                        BackupTransportClient transport =
                                 mTransportConnection.connectOrThrow(callerLogString);
                         transport.abortFullRestore();
                     } catch (Exception e) {
@@ -1070,7 +1076,7 @@
         // The app has timed out handling a restoring file
         @Override
         public void handleCancel(boolean cancelAll) {
-            backupManagerService.removeOperation(mEphemeralOpToken);
+            mOperationStorage.removeOperation(mEphemeralOpToken);
             if (DEBUG) {
                 Slog.w(TAG, "Full-data restore target timed out; shutting down");
             }
@@ -1095,7 +1101,7 @@
 
         String callerLogString = "PerformUnifiedRestoreTask.finalizeRestore()";
         try {
-            IBackupTransport transport =
+            BackupTransportClient transport =
                     mTransportConnection.connectOrThrow(callerLogString);
             transport.finishRestore();
         } catch (Exception e) {
@@ -1267,7 +1273,7 @@
 
     @Override
     public void operationComplete(long unusedResult) {
-        backupManagerService.removeOperation(mEphemeralOpToken);
+        mOperationStorage.removeOperation(mEphemeralOpToken);
         if (MORE_DEBUG) {
             Slog.i(TAG, "operationComplete() during restore: target="
                     + mCurrentPackage.packageName
@@ -1330,7 +1336,7 @@
     // A call to agent.doRestore() or agent.doRestoreFinished() has timed out
     @Override
     public void handleCancel(boolean cancelAll) {
-        backupManagerService.removeOperation(mEphemeralOpToken);
+        mOperationStorage.removeOperation(mEphemeralOpToken);
         Slog.e(TAG, "Timeout restoring application " + mCurrentPackage.packageName);
         mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
                 BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT,
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 652386f..bd1ac2dc 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -40,8 +40,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 
 import com.google.android.collect.Sets;
@@ -237,7 +237,7 @@
             }
             if (transportConnection != null) {
                 try {
-                    IBackupTransport transport =
+                    BackupTransportClient transport =
                             transportConnection.connectOrThrow(
                                     "AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport");
                     return transport.isAppEligibleForBackup(
diff --git a/services/cloudsearch/OWNERS b/services/cloudsearch/OWNERS
new file mode 100644
index 0000000..aa4da3b
--- /dev/null
+++ b/services/cloudsearch/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 758286
+
+huiwu@google.com
+srazdan@google.com
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index e3926b4..d3ef6dc 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -24,5 +24,8 @@
         type: "stream",
     },
     srcs: [":services.companion-sources"],
-    libs: ["services.core"],
+    libs: [
+        "app-compat-annotations",
+        "services.core",
+    ],
 }
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a0a00f7..637994f 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -16,11 +16,16 @@
 
 package com.android.server.companion;
 
+import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
+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.content.ComponentName.createRelative;
+
 import static com.android.internal.util.CollectionUtils.filter;
-import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
 import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
 import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
-import static com.android.server.companion.PermissionsUtils.enforceCallerPermissionsToRequest;
+import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
 import static com.android.server.companion.RolesUtils.isRoleHolder;
 
 import static java.util.Objects.requireNonNull;
@@ -28,73 +33,114 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.PendingIntent;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
-import android.companion.CompanionDeviceManager;
 import android.companion.IAssociationRequestCallback;
-import android.companion.ICompanionDeviceDiscoveryService;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.Signature;
+import android.net.MacAddress;
 import android.os.Binder;
-import android.os.IBinder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.util.PackageUtils;
 import android.util.Slog;
 
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.PerUser;
-import com.android.internal.infra.ServiceConnector;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.FgThread;
 
-import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.Objects;
 import java.util.Set;
 
+/**
+ * Class responsible for handling incoming {@link AssociationRequest}s.
+ * The main responsibilities of an {@link AssociationRequestsProcessor} are:
+ * <ul>
+ * <li> Requests validation and checking if the package that would own the association holds all
+ * necessary permissions.
+ * <li> Communication with the requester via a provided
+ * {@link android.companion.CompanionDeviceManager.Callback}.
+ * <li> Constructing an {@link Intent} for collecting user's approval (if needed), and handling the
+ * approval.
+ * <li> Calling to {@link CompanionDeviceManagerService} to create an association when/if the
+ * request was found valid and was approved by user.
+ * </ul>
+ *
+ * The class supports two variants of the "Association Flow": the full variant, and the shortened
+ * (a.k.a. No-UI) variant.
+ * Both flows start similarly: in
+ * {@link #processNewAssociationRequest(AssociationRequest, String, int, IAssociationRequestCallback)}
+ * invoked from
+ * {@link CompanionDeviceManagerService.CompanionDeviceManagerImpl#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
+ * method call.
+ * Then an {@link AssociationRequestsProcessor} makes a decision whether user's confirmation is
+ * required.
+ *
+ * If the user's approval is NOT required: an {@link AssociationRequestsProcessor} invokes
+ * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback)}
+ * which after calling to  {@link CompanionDeviceManagerService} to create an association, notifies
+ * the requester via
+ * {@link android.companion.CompanionDeviceManager.Callback#onAssociationCreated(AssociationInfo)}.
+ *
+ * If the user's approval is required: an {@link AssociationRequestsProcessor} constructs a
+ * {@link PendingIntent} for the approval UI and sends it back to the requester via
+ * {@link android.companion.CompanionDeviceManager.Callback#onAssociationPending(IntentSender)}.
+ * When/if user approves the request,  {@link AssociationRequestsProcessor} receives a "callback"
+ * from the Approval UI in via {@link #mOnRequestConfirmationReceiver} and invokes
+ * {@link #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback, ResultReceiver, MacAddress)}
+ * which one more time checks that the packages holds all necessary permissions before proceeding to
+ * {@link #createAssociationAndNotifyApplication(AssociationRequest, String, int, MacAddress, IAssociationRequestCallback)}.
+ *
+ * @see #processNewAssociationRequest(AssociationRequest, String, int, IAssociationRequestCallback)
+ * @see #processAssociationRequestApproval(AssociationRequest, IAssociationRequestCallback,
+ * ResultReceiver, MacAddress)
+ */
 class AssociationRequestsProcessor {
     private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";
 
-    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
-            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
-            ".CompanionDeviceDiscoveryService");
+    private static final ComponentName ASSOCIATION_REQUEST_APPROVAL_ACTIVITY =
+            createRelative(COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME, ".CompanionDeviceActivity");
+
+    // AssociationRequestsProcessor <-> UI
+    private static final String EXTRA_APPLICATION_CALLBACK = "application_callback";
+    private static final String EXTRA_ASSOCIATION_REQUEST = "association_request";
+    private static final String EXTRA_RESULT_RECEIVER = "result_receiver";
+
+    // AssociationRequestsProcessor -> UI
+    private static final int RESULT_CODE_ASSOCIATION_CREATED = 0;
+    private static final String EXTRA_ASSOCIATION = "association";
+
+    // UI -> AssociationRequestsProcessor
+    private static final int RESULT_CODE_ASSOCIATION_APPROVED = 0;
+    private static final String EXTRA_MAC_ADDRESS = "mac_address";
 
     private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5;
     private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;
 
     private final Context mContext;
     private final CompanionDeviceManagerService mService;
-    private final PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
-
-    private AssociationRequest mRequest;
-    private IAssociationRequestCallback mAppCallback;
-    private AndroidFuture<?> mOngoingDeviceDiscovery;
+    private final PackageManagerInternal mPackageManager;
 
     AssociationRequestsProcessor(CompanionDeviceManagerService service) {
         mContext = service.getContext();
         mService = service;
-
-        final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
-        mServiceConnectors = new PerUser<>() {
-            @Override
-            protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) {
-                return new ServiceConnector.Impl<>(
-                    mContext,
-                    serviceIntent, 0/* bindingFlags */, userId,
-                    ICompanionDeviceDiscoveryService.Stub::asInterface);
-            }
-        };
+        mPackageManager = service.mPackageManagerInternal;
     }
 
     /**
      * Handle incoming {@link AssociationRequest}s, sent via
      * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
      */
-    void process(@NonNull AssociationRequest request, @NonNull String packageName,
-            @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) {
+    void processNewAssociationRequest(@NonNull AssociationRequest request,
+            @NonNull String packageName, @UserIdInt int userId,
+            @NonNull IAssociationRequestCallback callback) {
         requireNonNull(request, "Request MUST NOT be null");
         if (request.isSelfManaged()) {
             requireNonNull(request.getDisplayName(), "AssociationRequest.displayName "
@@ -103,14 +149,15 @@
         requireNonNull(packageName, "Package name MUST NOT be null");
         requireNonNull(callback, "Callback MUST NOT be null");
 
+        final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
         if (DEBUG) {
-            Slog.d(TAG, "process() "
+            Slog.d(TAG, "processNewAssociationRequest() "
                     + "request=" + request + ", "
-                    + "package=u" + userId + "/" + packageName);
+                    + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")");
         }
 
         // 1. Enforce permissions and other requirements.
-        enforceCallerPermissionsToRequest(mContext, request, packageName, userId);
+        enforcePermissionsForAssociation(mContext, request, packageUid);
         mService.checkUsesFeature(packageName, userId);
 
         // 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
@@ -118,71 +165,99 @@
         if (request.isSelfManaged() && !request.isForceConfirmation()
                 && !willAddRoleHolder(request, packageName, userId)) {
             // 2a. Create association right away.
-            final AssociationInfo association = mService.createAssociation(userId, packageName,
-                /* macAddress */ null, request.getDisplayName(), request.getDeviceProfile(),
-                /* selfManaged */true);
-            withCatchingRemoteException(() -> callback.onAssociationCreated(association));
+            createAssociationAndNotifyApplication(request, packageName, userId,
+                    /*macAddress*/ null, callback);
             return;
         }
 
-        // 2b. Launch the UI.
-        synchronized (mService.mLock) {
-            if (mRequest != null) {
-                Slog.w(TAG, "CDM is already processing another AssociationRequest.");
+        // 2b. Build a PendingIntent for launching the confirmation UI, and send it back to the app:
 
-                withCatchingRemoteException(() -> callback.onFailure("Busy."));
-            }
+        // 2b.1. Populate the request with required info.
+        request.setPackageName(packageName);
+        request.setUserId(userId);
+        request.setSkipPrompt(mayAssociateWithoutPrompt(request, packageName, userId));
 
-            final boolean linked = withCatchingRemoteException(
-                    () -> callback.asBinder().linkToDeath(mBinderDeathRecipient, 0));
-            if (!linked) {
-                // The process has died by now: do not proceed.
-                return;
-            }
+        // 2b.2. Prepare extras and create an Intent.
+        final Bundle extras = new Bundle();
+        extras.putParcelable(EXTRA_ASSOCIATION_REQUEST, request);
+        extras.putBinder(EXTRA_APPLICATION_CALLBACK, callback.asBinder());
+        extras.putParcelable(EXTRA_RESULT_RECEIVER, prepareForIpc(mOnRequestConfirmationReceiver));
 
-            mRequest = request;
+        final Intent intent = new Intent();
+        intent.setComponent(ASSOCIATION_REQUEST_APPROVAL_ACTIVITY);
+        intent.putExtras(extras);
+
+        // 2b.3. Create a PendingIntent.
+        final PendingIntent pendingIntent;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // Using uid of the application that will own the association (usually the same
+            // application that sent the request) allows us to have multiple "pending" association
+            // requests at the same time.
+            // If the application already has a pending association request, that PendingIntent
+            // will be cancelled.
+            pendingIntent = PendingIntent.getActivity(mContext, /*requestCode */ packageUid, intent,
+                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
 
-        mAppCallback = callback;
-        request.setCallingPackage(packageName);
+        // 2b.4. Send the PendingIntent back to the app.
+        try {
+            callback.onAssociationPending(pendingIntent);
+        } catch (RemoteException ignore) { }
+    }
 
-        if (mayAssociateWithoutPrompt(packageName, userId)) {
-            Slog.i(TAG, "setSkipPrompt(true)");
-            request.setSkipPrompt(true);
+    private void processAssociationRequestApproval(@NonNull AssociationRequest request,
+            @NonNull IAssociationRequestCallback callback,
+            @NonNull ResultReceiver resultReceiver, @Nullable MacAddress macAddress) {
+        final String packageName = request.getPackageName();
+        final int userId = request.getUserId();
+        final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
+
+        if (DEBUG) {
+            Slog.d(TAG, "processAssociationRequestApproval()\n"
+                    + "   package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n"
+                    + "   request=" + request + "\n"
+                    + "   macAddress=" + macAddress + "\n");
         }
 
-        final String deviceProfile = request.getDeviceProfile();
-        mOngoingDeviceDiscovery = getDeviceProfilePermissionDescription(deviceProfile)
-                .thenComposeAsync(description -> {
-                    if (DEBUG) {
-                        Slog.d(TAG, "fetchProfileDescription done: " + description);
-                    }
+        // 1. Need to check permissions again in case something changed, since we first received
+        // this request.
+        try {
+            enforcePermissionsForAssociation(mContext, request, packageUid);
+        } catch (SecurityException e) {
+            // Since, at this point the caller is our own UI, we need to catch the exception on
+            // forward it back to the application via the callback.
+            try {
+                callback.onFailure(e.getMessage());
+            } catch (RemoteException ignore) { }
+            return;
+        }
 
-                    request.setDeviceProfilePrivilegesDescription(description);
+        // 2. Create association and notify the application.
+        final AssociationInfo association = createAssociationAndNotifyApplication(
+                request, packageName, userId, macAddress, callback);
 
-                    return mServiceConnectors.forUser(userId).postAsync(service -> {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Connected to CDM service -> "
-                                    + "Starting discovery for " + request);
-                        }
+        // 3. Send the association back the Approval Activity, so that it can report back to the app
+        // via Activity.setResult().
+        final Bundle data = new Bundle();
+        data.putParcelable(EXTRA_ASSOCIATION, association);
+        resultReceiver.send(RESULT_CODE_ASSOCIATION_CREATED, data);
+    }
 
-                        AndroidFuture<String> future = new AndroidFuture<>();
-                        service.startDiscovery(request, packageName, callback, future);
-                        return future;
-                    }).cancelTimeout();
+    private AssociationInfo createAssociationAndNotifyApplication(
+            @NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId,
+            @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) {
+        final AssociationInfo association = mService.createAssociation(userId, packageName,
+                macAddress, request.getDisplayName(), request.getDeviceProfile(),
+                request.isSelfManaged());
 
-                }, FgThread.getExecutor()).whenComplete(uncheckExceptions((deviceAddress, err) -> {
-                    if (err == null) {
-                        mService.legacyCreateAssociation(
-                                userId, deviceAddress, packageName, deviceProfile);
-                        mServiceConnectors.forUser(userId).post(
-                                ICompanionDeviceDiscoveryService::onAssociationCreated);
-                    } else {
-                        Slog.e(TAG, "Failed to discover device(s)", err);
-                        callback.onFailure("No devices found: " + err.getMessage());
-                    }
-                    cleanup();
-                }));
+        try {
+            callback.onAssociationCreated(association);
+        } catch (RemoteException ignore) { }
+
+        return association;
     }
 
     private boolean willAddRoleHolder(@NonNull AssociationRequest request,
@@ -197,26 +272,44 @@
         return !isRoleHolder;
     }
 
-    private void cleanup() {
-        if (DEBUG) {
-            Slog.d(TAG, "cleanup(); discovery = "
-                    + mOngoingDeviceDiscovery + ", request = " + mRequest);
-        }
-        synchronized (mService.mLock) {
-            AndroidFuture<?> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
-            if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
-                ongoingDeviceDiscovery.cancel(true);
+    private final ResultReceiver mOnRequestConfirmationReceiver =
+            new ResultReceiver(Handler.getMain()) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle data) {
+            if (DEBUG) {
+                Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() "
+                        + "code=" + resultCode + ", " + "data=" + data);
             }
-            if (mAppCallback != null) {
-                mAppCallback.asBinder().unlinkToDeath(mBinderDeathRecipient, 0);
-                mAppCallback = null;
-            }
-            mRequest = null;
-        }
-    }
 
-    private boolean mayAssociateWithoutPrompt(String packageName, int userId) {
-        final String deviceProfile = mRequest.getDeviceProfile();
+            if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
+                Slog.w(TAG, "Unknown result code:" + resultCode);
+                return;
+            }
+
+            final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST);
+            final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
+                    .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
+            final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER);
+
+            requireNonNull(request);
+            requireNonNull(callback);
+            requireNonNull(resultReceiver);
+
+            final MacAddress macAddress;
+            if (request.isSelfManaged()) {
+                macAddress = null;
+            } else {
+                macAddress = data.getParcelable(EXTRA_MAC_ADDRESS);
+                requireNonNull(macAddress);
+            }
+
+            processAssociationRequestApproval(request, callback, resultReceiver, macAddress);
+        }
+    };
+
+    private boolean mayAssociateWithoutPrompt(@NonNull AssociationRequest request,
+            @NonNull String packageName, @UserIdInt int userId) {
+        final String deviceProfile = request.getDeviceProfile();
         if (deviceProfile != null) {
             final boolean isRoleHolder = Binder.withCleanCallingIdentity(
                     () -> isRoleHolder(mContext, userId, packageName, deviceProfile));
@@ -252,8 +345,8 @@
         String[] sameOemCerts = mContext.getResources()
                 .getStringArray(com.android.internal.R.array.config_companionDeviceCerts);
 
-        Signature[] signatures = mService.mPackageManagerInternal
-                .getPackage(packageName).getSigningDetails().getSignatures();
+        Signature[] signatures = mPackageManager.getPackage(packageName).getSigningDetails()
+                .getSignatures();
         String[] apkCerts = PackageUtils.computeSignaturesSha256Digests(signatures);
 
         Set<String> sameOemPackageCerts =
@@ -274,47 +367,6 @@
         return false;
     }
 
-    @NonNull
-    private AndroidFuture<String> getDeviceProfilePermissionDescription(
-            @Nullable String deviceProfile) {
-        if (deviceProfile == null) {
-            return AndroidFuture.completedFuture(null);
-        }
-
-        final AndroidFuture<String> result = new AndroidFuture<>();
-        mService.mPermissionControllerManager.getPrivilegesDescriptionStringForProfile(
-                deviceProfile, FgThread.getExecutor(), desc -> {
-                    try {
-                        result.complete(String.valueOf(desc));
-                    } catch (Exception e) {
-                        result.completeExceptionally(e);
-                    }
-                });
-        return result;
-    }
-
-
-    void dump(@NonNull PrintWriter pw) {
-        pw.append("Discovery Service State:").append('\n');
-        for (int i = 0, size = mServiceConnectors.size(); i < size; i++) {
-            int userId = mServiceConnectors.keyAt(i);
-            pw.append("  ")
-                    .append("u").append(Integer.toString(userId)).append(": ")
-                    .append(Objects.toString(mServiceConnectors.valueAt(i)))
-                    .append('\n');
-        }
-    }
-
-    private final IBinder.DeathRecipient mBinderDeathRecipient = new IBinder.DeathRecipient() {
-        @Override
-        public void binderDied() {
-            if (DEBUG) {
-                Slog.d(TAG, "binderDied()");
-            }
-            mService.mMainHandler.post(AssociationRequestsProcessor.this::cleanup);
-        }
-    };
-
     private static Set<String> getSameOemPackageCerts(
             String packageName, String[] oemPackages, String[] sameOemCerts) {
         Set<String> sameOemPackageCerts = new HashSet<>();
@@ -330,16 +382,19 @@
         return sameOemPackageCerts;
     }
 
-    private static boolean withCatchingRemoteException(ThrowingRunnable runnable) {
-        try {
-            runnable.run();
-        } catch (RemoteException e) {
-            return false;
-        }
-        return true;
-    }
+    /**
+     * Convert an instance of a "locally-defined" ResultReceiver to an instance of
+     * {@link android.os.ResultReceiver} itself, which the receiving process will be able to
+     * unmarshall.
+     */
+    private static <T extends ResultReceiver> ResultReceiver prepareForIpc(T resultReceiver) {
+        final Parcel parcel = Parcel.obtain();
+        resultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
 
-    private interface ThrowingRunnable {
-        void run() throws RemoteException;
+        final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        return ipcFriendly;
     }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 049018cf..626128a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -39,7 +39,8 @@
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice;
 import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
 import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
@@ -176,6 +177,7 @@
             new BluetoothDeviceConnectedListener();
     private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver();
     private List<String> mCurrentlyConnectedDevices = new ArrayList<>();
+    Set<Integer> mPresentSelfManagedDevices = new HashSet<>();
     private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>();
     private UnbindDeviceListenersRunnable
             mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable();
@@ -216,7 +218,7 @@
         mPermissionControllerManager = requireNonNull(
                 context.getSystemService(PermissionControllerManager.class));
         mUserManager = context.getSystemService(UserManager.class);
-        mCompanionDevicePresenceController = new CompanionDevicePresenceController();
+        mCompanionDevicePresenceController = new CompanionDevicePresenceController(this);
         mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);
 
         registerPackageMonitor();
@@ -401,16 +403,17 @@
             Slog.i(LOG_TAG, "associate() "
                     + "request=" + request + ", "
                     + "package=u" + userId + "/" + packageName);
-            mAssociationRequestsProcessor.process(request, packageName, userId, callback);
+            enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
+                    "create associations");
+
+            mAssociationRequestsProcessor.processNewAssociationRequest(
+                    request, packageName, userId, callback);
         }
 
         @Override
         public List<AssociationInfo> getAssociations(String packageName, int userId) {
-            final int callingUid = getCallingUserId();
-            if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
-                throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
-                        + "permissions to get associations for u" + userId + "/" + packageName);
-            }
+            enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
+                    "get associations");
 
             if (!checkCallerCanManageCompanionDevice(getContext())) {
                 // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
@@ -425,7 +428,7 @@
         @Override
         public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
             enforceCallerCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser");
+            enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser");
 
             return new ArrayList<>(
                     CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
@@ -435,7 +438,7 @@
         public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
                 int userId) {
             enforceCallerCanInteractWithUserId(getContext(), userId);
-            enforceCallerCanManagerCompanionDevice(getContext(),
+            enforceCallerCanManageCompanionDevice(getContext(),
                     "addOnAssociationsChangedListener");
 
             //TODO: Implement.
@@ -554,6 +557,57 @@
             //TODO: b/199427116
         }
 
+        @Override
+        public void notifyDeviceAppeared(int associationId) {
+            final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+            if (association == null) {
+                throw new IllegalArgumentException("Association with ID " + associationId + " "
+                        + "does not exist "
+                        + "or belongs to a different package "
+                        + "or belongs to a different user");
+            }
+
+            if (!association.isSelfManaged()) {
+                throw new IllegalArgumentException("Association with ID " + associationId
+                        + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+                        + " self-managed associations.");
+            }
+
+            if (!mPresentSelfManagedDevices.add(associationId)) {
+                Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present");
+                return;
+            }
+
+            mCompanionDevicePresenceController.onDeviceNotifyAppeared(
+                    association, getContext(), mMainHandler);
+        }
+
+        @Override
+        public void notifyDeviceDisappeared(int associationId) {
+            final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+            if (association == null) {
+                throw new IllegalArgumentException("Association with ID " + associationId + " "
+                        + "does not exist "
+                        + "or belongs to a different package "
+                        + "or belongs to a different user");
+            }
+
+            if (!association.isSelfManaged()) {
+                throw new IllegalArgumentException("Association with ID " + associationId
+                        + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+                        + " self-managed associations.");
+            }
+
+            if (!mPresentSelfManagedDevices.contains(associationId)) {
+                Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected");
+                return;
+            }
+
+            mPresentSelfManagedDevices.remove(associationId);
+            mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind(
+                    association, getContext(), mMainHandler);
+        }
+
         private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
                 boolean active) throws RemoteException {
             getContext().enforceCallingOrSelfPermission(
@@ -622,7 +676,7 @@
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
-            enforceCallerCanManagerCompanionDevice(getContext(), "onShellCommand");
+            enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
             new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
@@ -644,11 +698,18 @@
                 }
 
             }
+
             fout.append("Currently Connected Devices:").append('\n');
             for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) {
                 fout.append("  ").append(mCurrentlyConnectedDevices.get(i)).append('\n');
             }
 
+            fout.append("Currently SelfManaged Connected Devices associationId:").append('\n');
+            for (Integer associationId : mPresentSelfManagedDevices) {
+                fout.append("  ").append("AssociationId:  ").append(
+                        String.valueOf(associationId)).append('\n');
+            }
+
             fout.append("Devices Last Nearby:").append('\n');
             for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) {
                 String device = mDevicesLastNearby.keyAt(i);
@@ -657,8 +718,6 @@
                         .append(sDateFormat.format(time)).append('\n');
             }
 
-            mAssociationRequestsProcessor.dump(fout);
-
             fout.append("Device Listener Services State:").append('\n');
             for (int i = 0, size =  mCompanionDevicePresenceController.mBoundServices.size();
                     i < size; i++) {
@@ -773,7 +832,9 @@
     }
 
     void onAssociationPreRemove(AssociationInfo association) {
-        if (association.isNotifyOnDeviceNearby()) {
+        if (association.isNotifyOnDeviceNearby()
+                || (association.isSelfManaged()
+                && mPresentSelfManagedDevices.contains(association.getId()))) {
             mCompanionDevicePresenceController.unbindDevicePresenceListener(
                     association.getPackageName(), association.getUserId());
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
index 3e00846..4447684 100644
--- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
+++ b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java
@@ -49,8 +49,10 @@
     private static final String LOG_TAG = "CompanionDevicePresenceController";
     PerUser<ArrayMap<String, List<BoundService>>> mBoundServices;
     private static final String META_DATA_KEY_PRIMARY = "primary";
+    private final CompanionDeviceManagerService mService;
 
-    public CompanionDevicePresenceController() {
+    public CompanionDevicePresenceController(CompanionDeviceManagerService service) {
+        mService = service;
         mBoundServices = new PerUser<ArrayMap<String, List<BoundService>>>() {
             @NonNull
             @Override
@@ -61,24 +63,45 @@
     }
 
     void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) {
-        ServiceConnector<ICompanionDeviceService> primaryConnector =
-                getPrimaryServiceConnector(association, context, handler);
-        if (primaryConnector != null) {
-            Slog.i(LOG_TAG,
-                    "Sending onDeviceAppeared to " + association.getPackageName() + ")");
-            primaryConnector.run(
-                    s -> s.onDeviceAppeared(association.getDeviceMacAddressAsString()));
+        for (BoundService boundService : getDeviceListenerServiceConnector(
+                association, context,  handler)) {
+            if (boundService.mIsPrimary) {
+                Slog.i(LOG_TAG,
+                        "Sending onDeviceAppeared to " + association.getPackageName() + ")");
+                boundService.mServiceConnector.run(
+                        service -> service.onDeviceAppeared(association));
+            } else {
+                Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName);
+                boundService.mServiceConnector.connect();
+            }
         }
     }
 
     void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) {
-        ServiceConnector<ICompanionDeviceService> primaryConnector =
-                getPrimaryServiceConnector(association, context, handler);
-        if (primaryConnector != null) {
-            Slog.i(LOG_TAG,
-                    "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
-            primaryConnector.run(
-                    s -> s.onDeviceDisappeared(association.getDeviceMacAddressAsString()));
+        for (BoundService boundService : getDeviceListenerServiceConnector(
+                association, context,  handler)) {
+            if (boundService.mIsPrimary) {
+                Slog.i(LOG_TAG,
+                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
+                boundService.mServiceConnector.run(service ->
+                        service.onDeviceDisappeared(association));
+            }
+        }
+    }
+
+    void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association,
+            Context context, Handler handler) {
+        for (BoundService boundService : getDeviceListenerServiceConnector(
+                association, context, handler)) {
+            if (boundService.mIsPrimary) {
+                Slog.i(LOG_TAG,
+                        "Sending onDeviceDisappeared to " + association.getPackageName() + ")");
+                boundService.mServiceConnector.post(
+                        service -> {
+                            service.onDeviceDisappeared(association);
+                        }).thenRun(() -> unbindDevicePresenceListener(
+                                        association.getPackageName(), association.getUserId()));
+            }
         }
     }
 
@@ -93,17 +116,6 @@
         }
     }
 
-    private ServiceConnector<ICompanionDeviceService> getPrimaryServiceConnector(
-            AssociationInfo association, Context context, Handler handler) {
-        for (BoundService boundService: getDeviceListenerServiceConnector(association, context,
-                handler)) {
-            if (boundService.mIsPrimary) {
-                return boundService.mServiceConnector;
-            }
-        }
-        return null;
-    }
-
     private List<BoundService> getDeviceListenerServiceConnector(AssociationInfo a, Context context,
             Handler handler) {
         return mBoundServices.forUser(a.getUserId()).computeIfAbsent(
@@ -140,18 +152,22 @@
                         protected long getAutoDisconnectTimeoutMs() {
                             // Service binding is managed manually based on corresponding device
                             // being nearby
-                            return Long.MAX_VALUE;
+                            return -1;
                         }
 
                         @Override
                         public void binderDied() {
                             super.binderDied();
-
-                            // Re-connect to the service if process gets killed
-                            handler.postDelayed(
-                                    this::connect,
-                                    CompanionDeviceManagerService
-                                            .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
+                            if (a.isSelfManaged()) {
+                                mBoundServices.forUser(a.getUserId()).remove(a.getPackageName());
+                                mService.mPresentSelfManagedDevices.remove(a.getId());
+                            } else {
+                                // Re-connect to the service if process gets killed
+                                handler.postDelayed(
+                                        this::connect,
+                                        CompanionDeviceManagerService
+                                                .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS);
+                            }
                         }
                     };
 
@@ -191,6 +207,13 @@
             }
         }
 
+        if (packageResolveInfos.size() > 1 && primaryCount == 0) {
+            Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService "
+                    + "to be bound when declare more than one CompanionDeviceService but "
+                    + association.getPackageName() + " has " + primaryCount);
+            return false;
+        }
+
         if (packageResolveInfos.size() == 1 && primaryCount != 0) {
             Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one"
                     + " CompanionDeviceService " + "but " + association.getPackageName()
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 0d38fa3..b981ff1 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -24,6 +24,7 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Binder.getCallingPid;
 import static android.os.Binder.getCallingUid;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.getCallingUserId;
@@ -65,21 +66,17 @@
         DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
     }
 
-    static void enforceCallerPermissionsToRequest(@NonNull Context context,
-            @NonNull AssociationRequest request, @NonNull String packageName,
-            @UserIdInt int userId) {
-        enforceCallerCanInteractWithUserId(context, userId);
-        enforceCallerIsSystemOr(userId, packageName);
-
-        enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile());
+    static void enforcePermissionsForAssociation(@NonNull Context context,
+            @NonNull AssociationRequest request, int packageUid) {
+        enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid);
 
         if (request.isSelfManaged()) {
-            enforceRequestSelfManagedPermission(context);
+            enforceRequestSelfManagedPermission(context, packageUid);
         }
     }
 
     static void enforceRequestDeviceProfilePermissions(
-            @NonNull Context context, @Nullable String deviceProfile) {
+            @NonNull Context context, @Nullable String deviceProfile, int packageUid) {
         // Device profile can be null.
         if (deviceProfile == null) return;
 
@@ -87,27 +84,16 @@
             throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
         }
 
-        if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
-            // TODO: remove, when properly supporting this profile.
-            throw new UnsupportedOperationException(
-                    "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
-        }
-
-        if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
-            // TODO: remove, when properly supporting this profile.
-            throw new UnsupportedOperationException(
-                    "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
-        }
-
         final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
-        if (context.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+        if (context.checkPermission(permission, getCallingPid(), packageUid)
+                != PERMISSION_GRANTED) {
             throw new SecurityException("Application must hold " + permission + " to associate "
                     + "with a device with " + deviceProfile + " profile.");
         }
     }
 
-    static void enforceRequestSelfManagedPermission(@NonNull Context context) {
-        if (context.checkCallingOrSelfPermission(REQUEST_COMPANION_SELF_MANAGED)
+    static void enforceRequestSelfManagedPermission(@NonNull Context context, int packageUid) {
+        if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid)
                 != PERMISSION_GRANTED) {
             throw new SecurityException("Application does not hold "
                     + REQUEST_COMPANION_SELF_MANAGED);
@@ -154,18 +140,39 @@
     }
 
     static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
-        if (getCallingUserId() == SYSTEM_UID) return true;
+        if (getCallingUid() == SYSTEM_UID) return true;
 
         return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
     }
 
-    static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
+    static void enforceCallerCanManageCompanionDevice(@NonNull Context context,
             @Nullable String message) {
-        if (getCallingUserId() == SYSTEM_UID) return;
+        if (getCallingUid() == SYSTEM_UID) return;
 
         context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
     }
 
+    static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
+            @UserIdInt int userId, @NonNull String packageName,
+            @Nullable String actionDescription) {
+        if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
+
+        throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+                + "permissions to "
+                + (actionDescription != null ? actionDescription : "manage associations")
+                + " for u" + userId + "/" + packageName);
+    }
+
+    /**
+     * Check if the caller is either:
+     * <ul>
+     * <li> the package itself
+     * <li> the System ({@link android.os.Process#SYSTEM_UID})
+     * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a
+     * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}.
+     * </ul>
+     * @return whether the caller is one of the above.
+     */
     static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
             @UserIdInt int userId, @NonNull String packageName) {
         if (checkCallerIsSystemOr(userId, packageName)) return true;
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
new file mode 100644
index 0000000..e98b63e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -0,0 +1,108 @@
+/*
+ * 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.companion.virtual;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.annotation.NonNull;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.window.DisplayWindowPolicyController;
+
+import java.util.HashSet;
+import java.util.List;
+
+
+/**
+ * A controller to control the policies of the windows that can be displayed on the virtual display.
+ */
+class GenericWindowPolicyController extends DisplayWindowPolicyController {
+
+    /**
+     * If required, allow the secure activity to display on remote device since
+     * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
+
+    @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
+
+    GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
+        setInterestedWindowFlags(windowFlags, systemWindowFlags);
+    }
+
+    @Override
+    public boolean canContainActivities(@NonNull List<ActivityInfo> activities) {
+        // Can't display all the activities if any of them don't want to be displayed.
+        final int activityCount = activities.size();
+        for (int i = 0; i < activityCount; i++) {
+            final ActivityInfo aInfo = activities.get(i);
+            if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
+            int systemWindowFlags) {
+        if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+            return false;
+        }
+        if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
+                activityInfo.packageName,
+                UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
+            // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
+            if ((windowFlags & FLAG_SECURE) != 0) {
+                return false;
+            }
+            if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onTopActivityChanged(ComponentName topActivity, int uid) {
+
+    }
+
+    @Override
+    public void onRunningAppsChanged(int[] runningUids) {
+        mRunningUids.clear();
+        for (int i = 0; i < runningUids.length; i++) {
+            mRunningUids.add(runningUids[i]);
+        }
+    }
+
+    /**
+     * Returns true if an app with the given UID has an activity running on the virtual display for
+     * this controller.
+     */
+    boolean containsUid(int uid) {
+        return mRunningUids.contains(uid);
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
new file mode 100644
index 0000000..067edcc
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -0,0 +1,283 @@
+/*
+ * 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.companion.virtual;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+/** Controls virtual input devices, including device lifecycle and event dispatch. */
+class InputController {
+
+    private final Object mLock;
+
+    /* Token -> file descriptor associations. */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    final Map<IBinder, Integer> mInputDeviceFds = new ArrayMap<>();
+
+    private final NativeWrapper mNativeWrapper;
+
+    InputController(@NonNull Object lock) {
+        this(lock, new NativeWrapper());
+    }
+
+    @VisibleForTesting
+    InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) {
+        mLock = lock;
+        mNativeWrapper = nativeWrapper;
+    }
+
+    void close() {
+        synchronized (mLock) {
+            for (int fd : mInputDeviceFds.values()) {
+                mNativeWrapper.closeUinput(fd);
+            }
+            mInputDeviceFds.clear();
+        }
+    }
+
+    void createKeyboard(@NonNull String deviceName,
+            int vendorId,
+            int productId,
+            @NonNull IBinder deviceToken) {
+        final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId);
+        if (fd < 0) {
+            throw new RuntimeException(
+                    "A native error occurred when creating keyboard: " + -fd);
+        }
+        synchronized (mLock) {
+            mInputDeviceFds.put(deviceToken, fd);
+        }
+        try {
+            deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Could not create virtual keyboard", e);
+        }
+    }
+
+    void createMouse(@NonNull String deviceName,
+            int vendorId,
+            int productId,
+            @NonNull IBinder deviceToken) {
+        final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId);
+        if (fd < 0) {
+            throw new RuntimeException(
+                    "A native error occurred when creating mouse: " + -fd);
+        }
+        synchronized (mLock) {
+            mInputDeviceFds.put(deviceToken, fd);
+        }
+        try {
+            deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Could not create virtual mouse", e);
+        }
+    }
+
+    void createTouchscreen(@NonNull String deviceName,
+            int vendorId,
+            int productId,
+            @NonNull IBinder deviceToken,
+            @NonNull Point screenSize) {
+        final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
+                screenSize.y, screenSize.x);
+        if (fd < 0) {
+            throw new RuntimeException(
+                    "A native error occurred when creating touchscreen: " + -fd);
+        }
+        synchronized (mLock) {
+            mInputDeviceFds.put(deviceToken, fd);
+        }
+        try {
+            deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Could not create virtual touchscreen", e);
+        }
+    }
+
+    void unregisterInputDevice(@NonNull IBinder token) {
+        synchronized (mLock) {
+            final Integer fd = mInputDeviceFds.remove(token);
+            if (fd == null) {
+                throw new IllegalArgumentException(
+                        "Could not unregister input device for given token");
+            }
+            mNativeWrapper.closeUinput(fd);
+        }
+    }
+
+    boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
+        synchronized (mLock) {
+            final Integer fd = mInputDeviceFds.get(token);
+            if (fd == null) {
+                throw new IllegalArgumentException(
+                        "Could not send key event to input device for given token");
+            }
+            return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction());
+        }
+    }
+
+    boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) {
+        synchronized (mLock) {
+            final Integer fd = mInputDeviceFds.get(token);
+            if (fd == null) {
+                throw new IllegalArgumentException(
+                        "Could not send button event to input device for given token");
+            }
+            return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction());
+        }
+    }
+
+    boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) {
+        synchronized (mLock) {
+            final Integer fd = mInputDeviceFds.get(token);
+            if (fd == null) {
+                throw new IllegalArgumentException(
+                        "Could not send touch event to input device for given token");
+            }
+            return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(),
+                    event.getAction(), event.getX(), event.getY(), event.getPressure(),
+                    event.getMajorAxisSize());
+        }
+    }
+
+    boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) {
+        synchronized (mLock) {
+            final Integer fd = mInputDeviceFds.get(token);
+            if (fd == null) {
+                throw new IllegalArgumentException(
+                        "Could not send relative event to input device for given token");
+            }
+            return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(),
+                    event.getRelativeY());
+        }
+    }
+
+    boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) {
+        synchronized (mLock) {
+            final Integer fd = mInputDeviceFds.get(token);
+            if (fd == null) {
+                throw new IllegalArgumentException(
+                        "Could not send scroll event to input device for given token");
+            }
+            return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(),
+                    event.getYAxisMovement());
+        }
+    }
+
+    public void dump(@NonNull PrintWriter fout) {
+        fout.println("    InputController: ");
+        synchronized (mLock) {
+            fout.println("      Active file descriptors: ");
+            for (int inputDeviceFd : mInputDeviceFds.values()) {
+                fout.println(inputDeviceFd);
+            }
+        }
+    }
+
+    private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
+            int productId);
+    private static native int nativeOpenUinputMouse(String deviceName, int vendorId,
+            int productId);
+    private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
+            int productId, int height, int width);
+    private static native boolean nativeCloseUinput(int fd);
+    private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
+    private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
+    private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType,
+            int action, float locationX, float locationY, float pressure, float majorAxisSize);
+    private static native boolean nativeWriteRelativeEvent(int fd, float relativeX,
+            float relativeY);
+    private static native boolean nativeWriteScrollEvent(int fd, float xAxisMovement,
+            float yAxisMovement);
+
+    /** Wrapper around the static native methods for tests. */
+    @VisibleForTesting
+    protected static class NativeWrapper {
+        public int openUinputKeyboard(String deviceName, int vendorId, int productId) {
+            return nativeOpenUinputKeyboard(deviceName, vendorId,
+                    productId);
+        }
+
+        public int openUinputMouse(String deviceName, int vendorId, int productId) {
+            return nativeOpenUinputMouse(deviceName, vendorId,
+                    productId);
+        }
+
+        public int openUinputTouchscreen(String deviceName, int vendorId, int productId, int height,
+                int width) {
+            return nativeOpenUinputTouchscreen(deviceName, vendorId,
+                    productId, height, width);
+        }
+
+        public boolean closeUinput(int fd) {
+            return nativeCloseUinput(fd);
+        }
+
+        public boolean writeKeyEvent(int fd, int androidKeyCode, int action) {
+            return nativeWriteKeyEvent(fd, androidKeyCode, action);
+        }
+
+        public boolean writeButtonEvent(int fd, int buttonCode, int action) {
+            return nativeWriteButtonEvent(fd, buttonCode, action);
+        }
+
+        public boolean writeTouchEvent(int fd, int pointerId, int toolType, int action,
+                float locationX, float locationY, float pressure, float majorAxisSize) {
+            return nativeWriteTouchEvent(fd, pointerId, toolType,
+                    action, locationX, locationY,
+                    pressure, majorAxisSize);
+        }
+
+        public boolean writeRelativeEvent(int fd, float relativeX, float relativeY) {
+            return nativeWriteRelativeEvent(fd, relativeX, relativeY);
+        }
+
+        public boolean writeScrollEvent(int fd, float xAxisMovement, float yAxisMovement) {
+            return nativeWriteScrollEvent(fd, xAxisMovement,
+                    yAxisMovement);
+        }
+    }
+
+    private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+
+        private final IBinder mDeviceToken;
+
+        BinderDeathRecipient(IBinder deviceToken) {
+            mDeviceToken = deviceToken;
+        }
+
+        @Override
+        public void binderDied() {
+            unregisterInputDevice(mDeviceToken);
+        }
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java
new file mode 100644
index 0000000..c397ea2
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java
@@ -0,0 +1,54 @@
+/*
+ * 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.companion.virtual;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Slog;
+
+/**
+ * Utility methods for checking permissions required for VirtualDeviceManager operations.
+ */
+class PermissionUtils {
+
+    private static final String LOG_TAG = "VDM.PermissionUtils";
+
+    /**
+     * Verifies whether the calling package name matches the calling app uid.
+     *
+     * @param context the context
+     * @param callingPackage the calling application package name
+     * @param callingUid the calling application uid
+     * @return {@code true} if the package name matches the calling app uid, {@code false} otherwise
+     */
+    public static boolean validatePackageName(Context context, String callingPackage,
+            int callingUid) {
+        try {
+            int packageUid = context.getPackageManager().getPackageUid(callingPackage, 0);
+            if (packageUid != callingUid) {
+                Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage
+                        + " is UID " + packageUid + " but caller is " + callingUid);
+                return false;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage
+                    + " does not exist");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
new file mode 100644
index 0000000..36edf4f
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -0,0 +1,302 @@
+/*
+ * 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.companion.virtual;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.annotation.NonNull;
+import android.companion.AssociationInfo;
+import android.companion.virtual.IVirtualDevice;
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.SparseArray;
+import android.window.DisplayWindowPolicyController;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+final class VirtualDeviceImpl extends IVirtualDevice.Stub
+        implements IBinder.DeathRecipient {
+
+    private final Object mVirtualDeviceLock = new Object();
+
+    private final Context mContext;
+    private final AssociationInfo mAssociationInfo;
+    private final int mOwnerUid;
+    private final InputController mInputController;
+    @VisibleForTesting
+    final List<Integer> mVirtualDisplayIds = new ArrayList<>();
+    private final OnDeviceCloseListener mListener;
+
+    /**
+     * A mapping from the virtual display ID to its corresponding
+     * {@link GenericWindowPolicyController}.
+     */
+    private final SparseArray<GenericWindowPolicyController> mWindowPolicyControllers =
+            new SparseArray<>();
+
+    VirtualDeviceImpl(Context context, AssociationInfo associationInfo,
+            IBinder token, int ownerUid, OnDeviceCloseListener listener) {
+        this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener);
+    }
+
+    @VisibleForTesting
+    VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token,
+            int ownerUid, InputController inputController, OnDeviceCloseListener listener) {
+        mContext = context;
+        mAssociationInfo = associationInfo;
+        mOwnerUid = ownerUid;
+        if (inputController == null) {
+            mInputController = new InputController(mVirtualDeviceLock);
+        } else {
+            mInputController = inputController;
+        }
+        mListener = listener;
+        try {
+            token.linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getAssociationId() {
+        return mAssociationInfo.getId();
+    }
+
+    @Override // Binder call
+    public void close() {
+        mListener.onClose(mAssociationInfo.getId());
+        mInputController.close();
+    }
+
+    @Override
+    public void binderDied() {
+        close();
+    }
+
+    @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,
+                "Permission required to create a virtual keyboard");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new SecurityException(
+                        "Cannot create a virtual keyboard for a display not associated with "
+                                + "this virtual device");
+            }
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @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,
+                "Permission required to create a virtual mouse");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new SecurityException(
+                        "Cannot create a virtual mouse for a display not associated with this "
+                                + "virtual device");
+            }
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInputController.createMouse(deviceName, vendorId, productId, deviceToken);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @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,
+                "Permission required to create a virtual touchscreen");
+        synchronized (mVirtualDeviceLock) {
+            if (!mVirtualDisplayIds.contains(displayId)) {
+                throw new SecurityException(
+                        "Cannot create a virtual touchscreen for a display not associated with "
+                                + "this virtual device");
+            }
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mInputController.createTouchscreen(deviceName, vendorId, productId,
+                    deviceToken, screenSize);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void unregisterInputDevice(IBinder token) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+                "Permission required to unregister this input device");
+
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            mInputController.unregisterInputDevice(token);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendKeyEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendButtonEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendTouchEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendRelativeEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override // Binder call
+    public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
+        final long binderToken = Binder.clearCallingIdentity();
+        try {
+            return mInputController.sendScrollEvent(token, event);
+        } finally {
+            Binder.restoreCallingIdentity(binderToken);
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        fout.println("  VirtualDevice: ");
+        fout.println("    mVirtualDisplayIds: ");
+        synchronized (mVirtualDeviceLock) {
+            for (int id : mVirtualDisplayIds) {
+                fout.println("      " + id);
+            }
+        }
+        mInputController.dump(fout);
+    }
+
+    DisplayWindowPolicyController onVirtualDisplayCreatedLocked(int displayId) {
+        if (mVirtualDisplayIds.contains(displayId)) {
+            throw new IllegalStateException(
+                    "Virtual device already have a virtual display with ID " + displayId);
+        }
+        mVirtualDisplayIds.add(displayId);
+        final GenericWindowPolicyController dwpc =
+                new GenericWindowPolicyController(FLAG_SECURE,
+                        SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+        mWindowPolicyControllers.put(displayId, dwpc);
+        return dwpc;
+    }
+
+    void onVirtualDisplayRemovedLocked(int displayId) {
+        if (!mVirtualDisplayIds.contains(displayId)) {
+            throw new IllegalStateException(
+                    "Virtual device doesn't have a virtual display with ID " + displayId);
+        }
+        mVirtualDisplayIds.remove(displayId);
+        mWindowPolicyControllers.remove(displayId);
+    }
+
+    int getOwnerUid() {
+        return mOwnerUid;
+    }
+
+    /**
+     * Returns true if an app with the given {@code uid} is currently running on this virtual
+     * device.
+     */
+    boolean isAppRunningOnVirtualDevice(int uid) {
+        final int size = mWindowPolicyControllers.size();
+        for (int i = 0; i < size; i++) {
+            if (mWindowPolicyControllers.valueAt(i).containsUid(uid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    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 18cf6f8..0db670e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -19,13 +19,19 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
+import android.companion.AssociationInfo;
+import android.companion.CompanionDeviceManager;
+import android.companion.CompanionDeviceManager.OnAssociationsChangedListener;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceManager;
 import android.content.Context;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ExceptionUtils;
 import android.util.Slog;
+import android.util.SparseArray;
+import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
@@ -33,18 +39,40 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
 
 
-/** @hide */
 @SuppressLint("LongLogTag")
 public class VirtualDeviceManagerService extends SystemService {
 
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "VirtualDeviceManagerService";
+
+    private final Object mVirtualDeviceManagerLock = new Object();
     private final VirtualDeviceManagerImpl mImpl;
-    @GuardedBy("mVirtualDevices")
-    private final ArrayList<VirtualDeviceImpl> mVirtualDevices = new ArrayList<>();
+
+    /**
+     * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for
+     * each CDM associated device.
+     */
+    @GuardedBy("mVirtualDeviceManagerLock")
+    private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>();
+
+    /**
+     * Mapping from user ID to CDM associations. The associations come from
+     * {@link CompanionDeviceManager#getAllAssociations()}, which contains associations across all
+     * packages.
+     */
+    private final ConcurrentHashMap<Integer, List<AssociationInfo>> mAllAssociations =
+            new ConcurrentHashMap<>();
+
+    /**
+     * Mapping from user ID to its change listener. The listeners are added when the user is
+     * started and removed when the user stops.
+     */
+    private final SparseArray<OnAssociationsChangedListener> mOnAssociationsChangedListeners =
+            new SparseArray<>();
 
     public VirtualDeviceManagerService(Context context) {
         super(context);
@@ -54,32 +82,108 @@
     @Override
     public void onStart() {
         publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
+        publishLocalService(VirtualDeviceManagerInternal.class, new LocalService());
     }
 
-    private class VirtualDeviceImpl extends IVirtualDevice.Stub {
+    @GuardedBy("mVirtualDeviceManagerLock")
+    private boolean isValidVirtualDeviceLocked(IVirtualDevice virtualDevice) {
+        try {
+            return mVirtualDevices.contains(virtualDevice.getAssociationId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
-        private VirtualDeviceImpl() {}
+    @Override
+    public void onUserStarting(@NonNull TargetUser user) {
+        super.onUserStarting(user);
+        synchronized (mVirtualDeviceManagerLock) {
+            final CompanionDeviceManager cdm = getContext()
+                    .createContextAsUser(user.getUserHandle(), 0)
+                    .getSystemService(CompanionDeviceManager.class);
+            final int userId = user.getUserIdentifier();
+            mAllAssociations.put(userId, cdm.getAllAssociations());
+            OnAssociationsChangedListener listener =
+                    associations -> mAllAssociations.put(userId, associations);
+            mOnAssociationsChangedListeners.put(userId, listener);
+            cdm.addOnAssociationsChangedListener(Runnable::run, listener);
+        }
+    }
 
-        @Override
-        public void close() {
-            synchronized (mVirtualDevices) {
-                mVirtualDevices.remove(this);
+    @Override
+    public void onUserStopping(@NonNull TargetUser user) {
+        super.onUserStopping(user);
+        synchronized (mVirtualDeviceManagerLock) {
+            int userId = user.getUserIdentifier();
+            mAllAssociations.remove(userId);
+            final CompanionDeviceManager cdm = getContext().createContextAsUser(
+                    user.getUserHandle(), 0)
+                    .getSystemService(CompanionDeviceManager.class);
+            OnAssociationsChangedListener listener = mOnAssociationsChangedListeners.get(userId);
+            if (listener != null) {
+                cdm.removeOnAssociationsChangedListener(listener);
+                mOnAssociationsChangedListeners.remove(userId);
             }
         }
     }
 
     class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
 
-        @Override
-        public IVirtualDevice createVirtualDevice() {
+        @Override // Binder call
+        public IVirtualDevice createVirtualDevice(
+                IBinder token, String packageName, int associationId) {
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
                     "createVirtualDevice");
-            VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl();
-            synchronized (mVirtualDevices) {
-                mVirtualDevices.add(virtualDevice);
+            final int callingUid = getCallingUid();
+            if (!PermissionUtils.validatePackageName(getContext(), packageName, callingUid)) {
+                throw new SecurityException(
+                        "Package name " + packageName + " does not belong to calling uid "
+                                + callingUid);
             }
-            return virtualDevice;
+            AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
+            if (associationInfo == null) {
+                throw new IllegalArgumentException("No association with ID " + associationId);
+            }
+            synchronized (mVirtualDeviceManagerLock) {
+                if (mVirtualDevices.contains(associationId)) {
+                    throw new IllegalStateException(
+                            "Virtual device for association ID " + associationId
+                                    + " already exists");
+                }
+                VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
+                        associationInfo, token, callingUid,
+                        new VirtualDeviceImpl.OnDeviceCloseListener() {
+                            @Override
+                            public void onClose(int associationId) {
+                                synchronized (mVirtualDeviceManagerLock) {
+                                    mVirtualDevices.remove(associationId);
+                                }
+                            }
+                        });
+                mVirtualDevices.put(associationInfo.getId(), virtualDevice);
+                return virtualDevice;
+            }
+        }
+
+        @Nullable
+        private AssociationInfo getAssociationInfo(String packageName, int associationId) {
+            final int callingUserId = getCallingUserHandle().getIdentifier();
+            final List<AssociationInfo> associations =
+                    mAllAssociations.get(callingUserId);
+            if (associations != null) {
+                final int associationSize = associations.size();
+                for (int i = 0; i < associationSize; i++) {
+                    AssociationInfo associationInfo = associations.get(i);
+                    if (associationInfo.belongsToPackage(callingUserId, packageName)
+                            && associationId == associationInfo.getId()) {
+                        return associationInfo;
+                    }
+                }
+            } else {
+                Slog.w(LOG_TAG, "No associations for user " + callingUserId);
+            }
+            return null;
         }
 
         @Override
@@ -101,11 +205,62 @@
                 return;
             }
             fout.println("Created virtual devices: ");
-            synchronized (mVirtualDevices) {
-                for (VirtualDeviceImpl virtualDevice : mVirtualDevices) {
-                    fout.println(virtualDevice.toString());
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mVirtualDevices.size(); i++) {
+                    mVirtualDevices.valueAt(i).dump(fd, fout, args);
                 }
             }
         }
     }
+
+    private final class LocalService extends VirtualDeviceManagerInternal {
+
+        @Override
+        public boolean isValidVirtualDevice(IVirtualDevice virtualDevice) {
+            synchronized (mVirtualDeviceManagerLock) {
+                return isValidVirtualDeviceLocked(virtualDevice);
+            }
+        }
+
+        @Override
+        public DisplayWindowPolicyController onVirtualDisplayCreated(IVirtualDevice virtualDevice,
+                int displayId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                return ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayCreatedLocked(displayId);
+            }
+        }
+
+        @Override
+        public void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                ((VirtualDeviceImpl) virtualDevice).onVirtualDisplayRemovedLocked(displayId);
+            }
+        }
+
+        @Override
+        public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    if (mVirtualDevices.valueAt(i).getOwnerUid() == uid) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        }
+
+        @Override
+        public boolean isAppRunningOnAnyVirtualDevice(int uid) {
+            synchronized (mVirtualDeviceManagerLock) {
+                int size = mVirtualDevices.size();
+                for (int i = 0; i < size; i++) {
+                    if (mVirtualDevices.valueAt(i).isAppRunningOnVirtualDevice(uid)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
 }
diff --git a/services/core/Android.bp b/services/core/Android.bp
index ba6854b..826b16a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -121,7 +121,7 @@
         "java/com/android/server/am/EventLogTags.logtags",
         "java/com/android/server/wm/EventLogTags.logtags",
         "java/com/android/server/policy/EventLogTags.logtags",
-        ":services.connectivity-nsd-sources",
+        ":services.connectivity-tiramisu-sources",
     ],
 
     libs: [
@@ -129,8 +129,6 @@
         "android.hardware.common-V2-java",
         "android.hardware.light-V2.0-java",
         "android.hardware.gnss-V2-java",
-        "android.hardware.power-V1-java",
-        "android.hardware.power-V1.0-java",
         "android.hardware.vibrator-V2-java",
         "app-compat-annotations",
         "framework-tethering.stubs.module_lib",
@@ -168,6 +166,7 @@
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.soundtrigger-V2.3-java",
         "android.hardware.power.stats-V1-java",
+        "android.hardware.power-V3-java",
         "android.hidl.manager-V1.2-java",
         "capture_state_listener-aidl-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index f3fad84..6fe2806 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -28,10 +28,6 @@
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.pm.PackageManager.ApplicationInfoFlags;
-import android.content.pm.PackageManager.ComponentInfoFlags;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.content.pm.PackageManager.ResolveInfoFlags;
 import android.content.pm.SigningDetails.CertCapabilities;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.component.ParsedMainComponent;
@@ -42,6 +38,7 @@
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.storage.StorageManager;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -198,7 +195,7 @@
      * @see PackageManager#getPackageInfo(String, int)
      */
     public abstract PackageInfo getPackageInfo(String packageName,
-            @PackageInfoFlags long flags, int filterCallingUid, int userId);
+            @PackageManager.PackageInfoFlagsBits long flags, int filterCallingUid, int userId);
 
     /**
      * Retrieve CE data directory inode number of an application.
@@ -227,7 +224,8 @@
      *         deleted with {@code DELETE_KEEP_DATA} flag set).
      */
     public abstract List<ApplicationInfo> getInstalledApplications(
-            @ApplicationInfoFlags long flags, @UserIdInt int userId, int callingUid);
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
+            int callingUid);
 
     /**
      * Retrieve launcher extras for a suspended package provided to the system in
@@ -324,7 +322,8 @@
      * @see PackageManager#getPackageUidAsUser(String, int, int)
      * @return The app's uid, or < 0 if the package was not found in that user
      */
-    public abstract int getPackageUid(String packageName, @PackageInfoFlags long flags, int userId);
+    public abstract int getPackageUid(String packageName,
+            @PackageManager.PackageInfoFlagsBits long flags, int userId);
 
     /**
      * Retrieve all of the information we know about a particular package/application.
@@ -333,7 +332,7 @@
      * @see PackageManager#getApplicationInfo(String, int)
      */
     public abstract ApplicationInfo getApplicationInfo(String packageName,
-            @ApplicationInfoFlags long flags, int filterCallingUid, int userId);
+            @PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId);
 
     /**
      * Retrieve all of the information we know about a particular activity class.
@@ -342,7 +341,7 @@
      * @see PackageManager#getActivityInfo(ComponentName, int)
      */
     public abstract ActivityInfo getActivityInfo(ComponentName component,
-            @ComponentInfoFlags long flags, int filterCallingUid, int userId);
+            @PackageManager.ComponentInfoFlagsBits long flags, int filterCallingUid, int userId);
 
     /**
      * Retrieve all activities that can be performed for the given intent.
@@ -353,22 +352,24 @@
      * @see PackageManager#queryIntentActivities(Intent, int)
      */
     public abstract List<ResolveInfo> queryIntentActivities(
-            Intent intent, @Nullable String resolvedType, @ResolveInfoFlags long flags,
-            int filterCallingUid, int userId);
+            Intent intent, @Nullable String resolvedType,
+            @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId);
 
 
     /**
      * Retrieve all receivers that can handle a broadcast of the given intent.
      */
     public abstract List<ResolveInfo> queryIntentReceivers(Intent intent,
-            String resolvedType, @ResolveInfoFlags long flags, int filterCallingUid, int userId);
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
+            int filterCallingUid, int userId);
 
     /**
      * Retrieve all services that can be performed for the given intent.
      * @see PackageManager#queryIntentServices(Intent, int)
      */
     public abstract List<ResolveInfo> queryIntentServices(
-            Intent intent, @ResolveInfoFlags long flags, int callingUid, int userId);
+            Intent intent, @PackageManager.ResolveInfoFlagsBits long flags, int callingUid,
+            int userId);
 
     /**
      * Interface to {@link com.android.server.pm.PackageManagerService#getHomeActivitiesAsUser}.
@@ -592,20 +593,21 @@
      * Resolves an activity intent, allowing instant apps to be resolved.
      */
     public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
-            @ResolveInfoFlags long flags, @PrivateResolveFlags long privateResolveFlags, int userId,
-            boolean resolveForStart, int filterCallingUid);
+            @PackageManager.ResolveInfoFlagsBits long flags,
+            @PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart,
+            int filterCallingUid);
 
     /**
     * Resolves a service intent, allowing instant apps to be resolved.
     */
     public abstract ResolveInfo resolveService(Intent intent, String resolvedType,
-            @ResolveInfoFlags long flags, int userId, int callingUid);
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid);
 
     /**
     * Resolves a content provider intent.
     */
-    public abstract ProviderInfo resolveContentProvider(String name, @ComponentInfoFlags long flags,
-            int userId, int callingUid);
+    public abstract ProviderInfo resolveContentProvider(String name,
+            @PackageManager.ComponentInfoFlagsBits long flags, int userId, int callingUid);
 
     /**
      * Track the creator of a new isolated uid.
@@ -872,12 +874,12 @@
      *
      * @throws IOException if the request was unable to be fulfilled.
      */
-    public abstract void freeStorage(String volumeUuid, long bytes, int storageFlags)
-            throws IOException;
+    public abstract void freeStorage(String volumeUuid, long bytes,
+            @StorageManager.AllocateFlags int flags) throws IOException;
 
     /** Returns {@code true} if the specified component is enabled and matches the given flags. */
     public abstract boolean isEnabledAndMatches(@NonNull ParsedMainComponent component,
-            @ComponentInfoFlags long flags, int userId);
+            @PackageManager.ComponentInfoFlagsBits long flags, int userId);
 
     /** Returns {@code true} if the given user requires extra badging for icons. */
     public abstract boolean userNeedsBadging(int userId);
@@ -1261,5 +1263,6 @@
     /**
      * Reconcile all app data for the given user.
      */
-    public abstract void reconcileAppsData(int userId, int storageFlags, boolean migrateAppsData);
+    public abstract void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
+            boolean migrateAppsData);
 }
diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
index 263ff18..380b1f3 100644
--- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
+++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
@@ -112,9 +112,9 @@
     void handleAirplaneModeChange() {
         if (shouldSkipAirplaneModeChange()) {
             Log.i(TAG, "Ignore airplane mode change");
-            // We have to store Bluetooth state here, so if user turns off Bluetooth
-            // after airplane mode is turned on, we don't forget to turn on Bluetooth
-            // when airplane mode turns off.
+            // Airplane mode enabled when Bluetooth is being used for audio/headering aid.
+            // Bluetooth is not disabled in such case, only state is changed to
+            // BLUETOOTH_ON_AIRPLANE mode.
             mAirplaneHelper.setSettingsInt(Settings.Global.BLUETOOTH_ON,
                     BluetoothManagerService.BLUETOOTH_ON_AIRPLANE);
             if (shouldPopToast()) {
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 8860a81..450e988 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1850,6 +1850,10 @@
                     mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
                     mEnable = true;
 
+                    if (isBle == 0) {
+                        persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+                    }
+
                     // Use service interface to get the exact state
                     try {
                         mBluetoothLock.readLock().lock();
@@ -1863,7 +1867,6 @@
                                     } else {
                                         Slog.w(TAG, "BT Enable in BLE_ON State, going to ON");
                                         mBluetooth.onLeServiceUp(mContext.getAttributionSource());
-                                        persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
                                     }
                                     break;
                                 case BluetoothAdapter.STATE_BLE_TURNING_ON:
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 25b36e8..34c21f2 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -88,6 +88,20 @@
     private static final int EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD = 5;
 
     /**
+     * Default value of the power button "cooldown" period after the Emergency gesture is triggered.
+     * See {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS}
+     */
+    private static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT = 3000;
+
+    /**
+     * Maximum value of the power button "cooldown" period after the Emergency gesture is triggered.
+     * The value read from {@link Settings.Secure#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS}
+     * is capped at this maximum.
+     */
+    @VisibleForTesting
+    static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
+
+    /**
      * Number of taps required to launch camera shortcut.
      */
     private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
@@ -163,7 +177,14 @@
      */
     private boolean mEmergencyGestureEnabled;
 
+    /**
+     * Power button cooldown period in milliseconds, after emergency gesture is triggered. A zero
+     * value means the cooldown period is disabled.
+     */
+    private int mEmergencyGesturePowerButtonCooldownPeriodMs;
+
     private long mLastPowerDown;
+    private long mLastEmergencyGestureTriggered;
     private int mPowerButtonConsecutiveTaps;
     private int mPowerButtonSlowConsecutiveTaps;
     private final UiEventLogger mUiEventLogger;
@@ -231,6 +252,7 @@
             updateCameraRegistered();
             updateCameraDoubleTapPowerEnabled();
             updateEmergencyGestureEnabled();
+            updateEmergencyGesturePowerButtonCooldownPeriodMs();
 
             mUserId = ActivityManager.getCurrentUser();
             mContext.registerReceiver(mUserReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
@@ -261,6 +283,10 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.EMERGENCY_GESTURE_ENABLED),
                 false, mSettingObserver, mUserId);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(
+                        Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS),
+                false, mSettingObserver, mUserId);
     }
 
     private void updateCameraRegistered() {
@@ -294,6 +320,14 @@
         }
     }
 
+    @VisibleForTesting
+    void updateEmergencyGesturePowerButtonCooldownPeriodMs() {
+        int cooldownPeriodMs = getEmergencyGesturePowerButtonCooldownPeriodMs(mContext, mUserId);
+        synchronized (this) {
+            mEmergencyGesturePowerButtonCooldownPeriodMs = cooldownPeriodMs;
+        }
+    }
+
     private void unregisterCameraLaunchGesture() {
         if (mCameraLaunchRegistered) {
             mCameraLaunchRegistered = false;
@@ -428,6 +462,20 @@
     }
 
     /**
+     * Gets power button cooldown period in milliseconds after emergency gesture is triggered. The
+     * value is capped at a maximum
+     * {@link GestureLauncherService#EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX}. If the
+     * value is zero, it means the cooldown period is disabled.
+     */
+    @VisibleForTesting
+    static int getEmergencyGesturePowerButtonCooldownPeriodMs(Context context, int userId) {
+        int cooldown = Settings.Secure.getIntForUser(context.getContentResolver(),
+                Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
+                EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_DEFAULT, userId);
+        return Math.min(cooldown, EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX);
+    }
+
+    /**
      * Whether to enable the camera launch gesture.
      */
     private static boolean isCameraLaunchEnabled(Resources resources) {
@@ -475,10 +523,24 @@
      */
     public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive,
             MutableBoolean outLaunched) {
+        if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
+                && event.getEventTime() - mLastEmergencyGestureTriggered
+                < mEmergencyGesturePowerButtonCooldownPeriodMs) {
+            Slog.i(TAG, String.format(
+                    "Suppressing power button: within %dms cooldown period after Emergency "
+                            + "Gesture. Begin=%dms, end=%dms.",
+                    mEmergencyGesturePowerButtonCooldownPeriodMs,
+                    mLastEmergencyGestureTriggered,
+                    mLastEmergencyGestureTriggered + mEmergencyGesturePowerButtonCooldownPeriodMs));
+            outLaunched.value = false;
+            return true;
+        }
+
         if (event.isLongPress()) {
             // Long presses are sent as a second key down. If the long press threshold is set lower
             // than the double tap of sequence interval thresholds, this could cause false double
             // taps or consecutive taps, so we want to ignore the long press event.
+            outLaunched.value = false;
             return false;
         }
         boolean launchCamera = false;
@@ -542,6 +604,12 @@
             Slog.i(TAG, "Emergency gesture detected, launching.");
             launchEmergencyGesture = handleEmergencyGesture();
             mUiEventLogger.log(GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
+            // Record emergency trigger time if emergency UI was launched
+            if (launchEmergencyGesture) {
+                synchronized (this) {
+                    mLastEmergencyGestureTriggered = event.getEventTime();
+                }
+            }
         }
         mMetricsLogger.histogram("power_consecutive_short_tap_count",
                 mPowerButtonSlowConsecutiveTaps);
@@ -670,6 +738,7 @@
                 updateCameraRegistered();
                 updateCameraDoubleTapPowerEnabled();
                 updateEmergencyGestureEnabled();
+                updateEmergencyGesturePowerButtonCooldownPeriodMs();
             }
         }
     };
@@ -680,6 +749,7 @@
                 updateCameraRegistered();
                 updateCameraDoubleTapPowerEnabled();
                 updateEmergencyGestureEnabled();
+                updateEmergencyGesturePowerButtonCooldownPeriodMs();
             }
         }
     };
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index b641377..71b463a 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -25,13 +25,14 @@
 per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
 per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
 per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
 per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
 per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
-per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file MmsServiceBroker.java = file:/telephony/OWNERS
 per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 5cc5201..1208cb7 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -22,19 +22,17 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.os.Build;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.ClassLoaderFactory;
+import com.android.internal.os.SystemServerClassLoaderFactory;
 import com.android.internal.util.Preconditions;
 import com.android.server.SystemService.TargetUser;
 import com.android.server.am.EventLogTags;
@@ -95,9 +93,6 @@
     // Services that should receive lifecycle events.
     private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
 
-    // Map of paths to PathClassLoader, so we don't load the same path multiple times.
-    private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>();
-
     private int mCurrentPhase = -1;
 
     private UserManagerInternal mUserManagerInternal;
@@ -142,16 +137,8 @@
      * @return The service instance.
      */
     public SystemService startServiceFromJar(String className, String path) {
-        PathClassLoader pathClassLoader = mLoadedPaths.get(path);
-        if (pathClassLoader == null) {
-            // NB: the parent class loader should always be the system server class loader.
-            // Changing it has implications that require discussion with the mainline team.
-            pathClassLoader = (PathClassLoader) ClassLoaderFactory.createClassLoader(
-                    path, null /* librarySearchPath */, null /* libraryPermittedPath */,
-                    this.getClass().getClassLoader(), Build.VERSION.SDK_INT,
-                    true /* isNamespaceShared */, null /* classLoaderName */);
-            mLoadedPaths.put(path, pathClassLoader);
-        }
+        PathClassLoader pathClassLoader = SystemServerClassLoaderFactory.getOrCreateClassLoader(
+                path, this.getClass().getClassLoader());
         final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
         return startService(serviceClass);
     }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 4775127..811f2f5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -91,6 +91,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ICarrierPrivilegesListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
@@ -106,6 +107,7 @@
 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;
@@ -149,9 +151,12 @@
         IPhoneStateListener callback;
         IOnSubscriptionsChangedListener onSubscriptionsChangedListenerCallback;
         IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
+        ICarrierPrivilegesListener carrierPrivilegesListener;
 
         int callerUid;
         int callerPid;
+        boolean renounceFineLocationAccess;
+        boolean renounceCoarseLocationAccess;
 
         Set<Integer> eventList;
 
@@ -171,6 +176,10 @@
             return (onOpportunisticSubscriptionsChangedListenerCallback != null);
         }
 
+        boolean matchCarrierPrivilegesListener() {
+            return carrierPrivilegesListener != null;
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -187,8 +196,9 @@
                     + " onSubscriptionsChangedListenererCallback="
                     + onSubscriptionsChangedListenerCallback
                     + " onOpportunisticSubscriptionsChangedListenererCallback="
-                    + onOpportunisticSubscriptionsChangedListenerCallback + " subId=" + subId
-                    + " phoneId=" + phoneId + " events=" + eventList + "}";
+                    + onOpportunisticSubscriptionsChangedListenerCallback
+                    + " carrierPrivilegesListener=" + carrierPrivilegesListener
+                    + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
         }
     }
 
@@ -364,7 +374,7 @@
 
     private List<BarringInfo> mBarringInfo = null;
 
-    private boolean mCarrierNetworkChangeState = false;
+    private boolean[] mCarrierNetworkChangeState = null;
 
     private PhoneCapability mPhoneCapability = null;
 
@@ -400,6 +410,10 @@
      */
     private List<Map<Pair<Integer, ApnSetting>, PreciseDataConnectionState>>
             mPreciseDataConnectionStates;
+
+    /** Per-phoneId snapshot of privileged packages (names + UIDs). */
+    private List<Pair<List<String>, int[]>> mCarrierPrivilegeStates;
+
     /**
      * Support backward compatibility for {@link android.telephony.TelephonyDisplayInfo}.
      */
@@ -673,6 +687,7 @@
         mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
         mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
         mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
+        mCarrierNetworkChangeState = copyOf(mCarrierNetworkChangeState, mNumPhones);
         mIsDataEnabled= copyOf(mIsDataEnabled, mNumPhones);
         mDataEnabledReason = copyOf(mDataEnabledReason, mNumPhones);
         mAllowedNetworkTypeReason = copyOf(mAllowedNetworkTypeReason, mNumPhones);
@@ -686,6 +701,7 @@
             cutListToSize(mBarringInfo, mNumPhones);
             cutListToSize(mPhysicalChannelConfigs, mNumPhones);
             cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
+            cutListToSize(mCarrierPrivilegeStates, mNumPhones);
             return;
         }
 
@@ -718,6 +734,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new ArrayMap<>());
             mBarringInfo.add(i, new BarringInfo());
+            mCarrierNetworkChangeState[i] = false;
             mTelephonyDisplayInfos[i] = null;
             mIsDataEnabled[i] = false;
             mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
@@ -725,6 +742,7 @@
             mAllowedNetworkTypeReason[i] = -1;
             mAllowedNetworkTypeValue[i] = -1;
             mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
+            mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
         }
     }
 
@@ -782,6 +800,7 @@
         mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
         mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
         mBarringInfo = new ArrayList<>();
+        mCarrierNetworkChangeState = new boolean[numPhones];
         mTelephonyDisplayInfos = new TelephonyDisplayInfo[numPhones];
         mPhysicalChannelConfigs = new ArrayList<>();
         mAllowedNetworkTypeReason = new int[numPhones];
@@ -789,6 +808,7 @@
         mIsDataEnabled = new boolean[numPhones];
         mDataEnabledReason = new int[numPhones];
         mLinkCapacityEstimateLists = new ArrayList<>();
+        mCarrierPrivilegeStates = new ArrayList<>();
 
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
@@ -818,6 +838,7 @@
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mPreciseDataConnectionStates.add(new ArrayMap<>());
             mBarringInfo.add(i, new BarringInfo());
+            mCarrierNetworkChangeState[i] = false;
             mTelephonyDisplayInfos[i] = null;
             mIsDataEnabled[i] = false;
             mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
@@ -825,6 +846,7 @@
             mAllowedNetworkTypeReason[i] = -1;
             mAllowedNetworkTypeValue[i] = -1;
             mLinkCapacityEstimateLists.add(i, INVALID_LCE_LIST);
+            mCarrierPrivilegeStates.add(i, new Pair<>(Collections.emptyList(), new int[0]));
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -995,14 +1017,25 @@
     }
 
     @Override
-    public void listenWithEventList(int subId, String callingPackage, String callingFeatureId,
-            IPhoneStateListener callback, int[] events, boolean notifyNow) {
+    public void listenWithEventList(boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess, int subId, String callingPackage,
+            String callingFeatureId, IPhoneStateListener callback,
+            int[] events, boolean notifyNow) {
         Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
-        listen(callingPackage, callingFeatureId, callback, eventList, notifyNow, subId);
+        listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+                callingFeatureId, callback, eventList, notifyNow, subId);
     }
 
     private void listen(String callingPackage, @Nullable String callingFeatureId,
             IPhoneStateListener callback, Set<Integer> events, boolean notifyNow, int subId) {
+        listen(false, false, callingPackage,
+                callingFeatureId, callback, events, notifyNow, subId);
+    }
+
+    private void listen(boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess, String callingPackage,
+            @Nullable String callingFeatureId, IPhoneStateListener callback,
+            Set<Integer> events, boolean notifyNow, int subId) {
         int callerUserId = UserHandle.getCallingUserId();
         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
         String str = "listen: E pkg=" + pii(callingPackage) + " uid=" + Binder.getCallingUid()
@@ -1047,6 +1080,8 @@
             r.callback = callback;
             r.callingPackage = callingPackage;
             r.callingFeatureId = callingFeatureId;
+            r.renounceCoarseLocationAccess = renounceCoarseLocationAccess;
+            r.renounceFineLocationAccess = renounceFineLocationAccess;
             r.callerUid = Binder.getCallingUid();
             r.callerPid = Binder.getCallingPid();
             // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
@@ -1215,7 +1250,7 @@
                 }
                 if (events.contains(TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)) {
                     try {
-                        r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState);
+                        r.callback.onCarrierNetworkChange(mCarrierNetworkChangeState[r.phoneId]);
                     } catch (RemoteException ex) {
                         remove(r.binder);
                     }
@@ -1709,23 +1744,37 @@
             throw new SecurityException("notifyCarrierNetworkChange without carrier privilege");
         }
 
-        synchronized (mRecords) {
-            mCarrierNetworkChangeState = active;
-            for (int subId : subIds) {
-                int phoneId = getPhoneIdFromSubId(subId);
+        for (int subId : subIds) {
+            notifyCarrierNetworkChangeWithPermission(subId, active);
+        }
+    }
 
-                if (VDBG) {
-                    log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
-                }
-                for (Record r : mRecords) {
-                    if (r.matchTelephonyCallbackEvent(
-                            TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
-                            && idMatch(r, subId, phoneId)) {
-                        try {
-                            r.callback.onCarrierNetworkChange(active);
-                        } catch (RemoteException ex) {
-                            mRemoveList.add(r.binder);
-                        }
+    @Override
+    public void notifyCarrierNetworkChangeWithSubId(int subId, boolean active) {
+        if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) {
+            throw new SecurityException(
+                    "notifyCarrierNetworkChange without carrier privilege on subId " + subId);
+        }
+
+        notifyCarrierNetworkChangeWithPermission(subId, active);
+    }
+
+    private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+        synchronized (mRecords) {
+            int phoneId = getPhoneIdFromSubId(subId);
+            mCarrierNetworkChangeState[phoneId] = active;
+
+            if (VDBG) {
+                log("notifyCarrierNetworkChange: active=" + active + "subId: " + subId);
+            }
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(
+                        TelephonyCallback.EVENT_CARRIER_NETWORK_CHANGED)
+                        && idMatch(r, subId, phoneId)) {
+                    try {
+                        r.callback.onCarrierNetworkChange(active);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
                     }
                 }
             }
@@ -2733,6 +2782,104 @@
     }
 
     @Override
+    public void addCarrierPrivilegesListener(
+            int phoneId,
+            ICarrierPrivilegesListener callback,
+            String callingPackage,
+            String callingFeatureId) {
+        int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "addCarrierPrivilegesListener");
+        if (VDBG) {
+            log(
+                    "listen carrier privs: E pkg=" + pii(callingPackage) + " phoneId=" + phoneId
+                            + " uid=" + Binder.getCallingUid()
+                            + " myUserId=" + UserHandle.myUserId() + " callerUserId=" + callerUserId
+                            + " callback=" + callback
+                            + " callback.asBinder=" + callback.asBinder());
+        }
+        if (!validatePhoneId(phoneId)) {
+            throw new IllegalArgumentException("Invalid slot index: " + phoneId);
+        }
+
+        synchronized (mRecords) {
+            Record r = add(
+                    callback.asBinder(), Binder.getCallingUid(), Binder.getCallingPid(), false);
+
+            if (r == null) return;
+
+            r.context = mContext;
+            r.carrierPrivilegesListener = callback;
+            r.callingPackage = callingPackage;
+            r.callingFeatureId = callingFeatureId;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.phoneId = phoneId;
+            r.eventList = new ArraySet<>();
+            if (DBG) {
+                log("listen carrier privs: Register r=" + r);
+            }
+
+            Pair<List<String>, int[]> state = mCarrierPrivilegeStates.get(phoneId);
+            try {
+                r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+                        Collections.unmodifiableList(state.first),
+                        Arrays.copyOf(state.second, state.second.length));
+            } catch (RemoteException ex) {
+                remove(r.binder);
+            }
+        }
+    }
+
+    @Override
+    public void removeCarrierPrivilegesListener(
+            ICarrierPrivilegesListener callback, String callingPackage) {
+        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                "removeCarrierPrivilegesListener");
+        remove(callback.asBinder());
+    }
+
+    @Override
+    public void notifyCarrierPrivilegesChanged(
+            int phoneId, List<String> privilegedPackageNames, int[] privilegedUids) {
+        if (!checkNotifyPermission("notifyCarrierPrivilegesChanged")) {
+            return;
+        }
+        if (!validatePhoneId(phoneId)) return;
+        if (VDBG) {
+            log(
+                    "notifyCarrierPrivilegesChanged: phoneId=" + phoneId
+                            + ", <packages=" + pii(privilegedPackageNames)
+                            + ", uids=" + Arrays.toString(privilegedUids) + ">");
+        }
+        synchronized (mRecords) {
+            mCarrierPrivilegeStates.set(
+                    phoneId, new Pair<>(privilegedPackageNames, privilegedUids));
+            for (Record r : mRecords) {
+                // Listeners are per-slot, not per-subscription. This is to provide a stable
+                // view across SIM profile switches.
+                if (!r.matchCarrierPrivilegesListener()
+                        || !idMatch(r, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phoneId)) {
+                    continue;
+                }
+                try {
+                    // Make sure even in-process listeners can't modify the values.
+                    r.carrierPrivilegesListener.onCarrierPrivilegesChanged(
+                            Collections.unmodifiableList(privilegedPackageNames),
+                            Arrays.copyOf(privilegedUids, privilegedUids.length));
+                } catch (RemoteException ex) {
+                    mRemoveList.add(r.binder);
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
 
@@ -2773,6 +2920,7 @@
                 pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
                 pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
                 pw.println("mBarringInfo=" + mBarringInfo.get(i));
+                pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState[i]);
                 pw.println("mTelephonyDisplayInfo=" + mTelephonyDisplayInfos[i]);
                 pw.println("mIsDataEnabled=" + mIsDataEnabled);
                 pw.println("mDataEnabledReason=" + mDataEnabledReason);
@@ -2780,9 +2928,14 @@
                 pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
                 pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
                 pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
+                // We need to obfuscate package names, and primitive arrays' native toString is ugly
+                Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
+                pw.println(
+                        "mCarrierPrivilegeState=<packages=" + pii(carrierPrivilegeState.first)
+                                + ", uids=" + Arrays.toString(carrierPrivilegeState.second) + ">");
                 pw.decreaseIndent();
             }
-            pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
+
             pw.println("mPhoneCapability=" + mPhoneCapability);
             pw.println("mActiveDataSubId=" + mActiveDataSubId);
             pw.println("mRadioPowerState=" + mRadioPowerState);
@@ -3199,6 +3352,9 @@
      * If you don't need app compat logic, use {@link #checkFineLocationAccess(Record)}.
      */
     private boolean checkFineLocationAccess(Record r, int minSdk) {
+        if (r.renounceFineLocationAccess) {
+            return false;
+        }
         LocationAccessPolicy.LocationPermissionQuery query =
                 new LocationAccessPolicy.LocationPermissionQuery.Builder()
                         .setCallingPackage(r.callingPackage)
@@ -3225,6 +3381,9 @@
      * If you don't need app compat logic, use {@link #checkCoarseLocationAccess(Record)}.
      */
     private boolean checkCoarseLocationAccess(Record r, int minSdk) {
+        if (r.renounceCoarseLocationAccess) {
+            return false;
+        }
         LocationAccessPolicy.LocationPermissionQuery query =
                 new LocationAccessPolicy.LocationPermissionQuery.Builder()
                         .setCallingPackage(r.callingPackage)
@@ -3500,4 +3659,10 @@
     private static String pii(String packageName) {
         return Build.IS_DEBUGGABLE ? packageName : "***";
     }
+
+    /** Redacts an entire list of package names if necessary. */
+    private static String pii(List<String> packageNames) {
+        if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
+        return "[***, size=" + packageNames.size() + "]";
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a33aa60..ea345a7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -93,6 +93,7 @@
 import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -307,6 +308,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.FeatureFlagUtils;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -8965,8 +8967,8 @@
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
+                mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             }
-            mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             if (dumpPackage == null) {
                 pw.println();
                 if (dumpAll) {
@@ -13372,12 +13374,7 @@
                                         !intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false);
                                 final boolean fullUninstall = removed && !replacing;
                                 if (removed) {
-                                    if (killProcess) {
-                                        forceStopPackageLocked(ssp, UserHandle.getAppId(
-                                                intent.getIntExtra(Intent.EXTRA_UID, -1)),
-                                                false, true, true, false, fullUninstall, userId,
-                                                removed ? "pkg removed" : "pkg changed");
-                                    } else {
+                                    if (!killProcess) {
                                         // Kill any app zygotes always, since they can't fork new
                                         // processes with references to the old code
                                         forceStopAppZygoteLocked(ssp, UserHandle.getAppId(
@@ -14598,6 +14595,8 @@
     private void checkExcessivePowerUsage() {
         updateCpuStatsNow();
 
+        final boolean monitorPhantomProcs = mSystemReady && FeatureFlagUtils.isEnabled(mContext,
+                SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         synchronized (mProcLock) {
             final boolean doCpuKills = mLastPowerCheckUptime != 0;
             final long curUptime = SystemClock.uptimeMillis();
@@ -14623,9 +14622,11 @@
 
                     updateAppProcessCpuTimeLPr(uptimeSince, doCpuKills, checkDur, cpuLimit, app);
 
-                    // Also check the phantom processes if there is any
-                    updatePhantomProcessCpuTimeLPr(
-                            uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+                    if (monitorPhantomProcs) {
+                        // Also check the phantom processes if there is any
+                        updatePhantomProcessCpuTimeLPr(
+                                uptimeSince, doCpuKills, checkDur, cpuLimit, app);
+                    }
                 }
             });
         }
@@ -15659,6 +15660,11 @@
         }
     }
 
+    @Override
+    public void enableBinderTracing() {
+        Binder.enableTracingForUid(Binder.getCallingUid());
+    }
+
     @VisibleForTesting
     public final class LocalService extends ActivityManagerInternal
             implements ActivityManagerLocal {
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 221de8d..d6a4cf6 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -77,6 +78,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DebugUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -1805,6 +1807,8 @@
     }
 
     void updateCpuStatsNow() {
+        final boolean monitorPhantomProcs = mService.mSystemReady && FeatureFlagUtils.isEnabled(
+                mService.mContext, SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
         synchronized (mProcessCpuTracker) {
             mProcessCpuMutexFree.set(false);
             final long now = SystemClock.uptimeMillis();
@@ -1843,7 +1847,7 @@
                 }
             }
 
-            if (haveNewCpuStats) {
+            if (monitorPhantomProcs && haveNewCpuStats) {
                 mService.mPhantomProcessList.updateProcessCpuStatesLocked(mProcessCpuTracker);
             }
 
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b7b4870..336572f 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -45,7 +45,6 @@
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.internal.power.MeasuredEnergyStats;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 
 import libcore.util.EmptyArray;
@@ -131,6 +130,9 @@
     @GuardedBy("this")
     private Future<?> mBatteryLevelSync;
 
+    @GuardedBy("this")
+    private Future<?> mProcessStateSync;
+
     // If both mStats and mWorkerLock need to be synchronized, mWorkerLock must be acquired first.
     private final Object mWorkerLock = new Object();
 
@@ -260,43 +262,6 @@
     }
 
     @Override
-    public Future<?> scheduleReadProcStateCpuTimes(
-            boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
-        synchronized (mStats) {
-            if (!mStats.trackPerProcStateCpuTimes()) {
-                return null;
-            }
-        }
-        synchronized (BatteryExternalStatsWorker.this) {
-            if (!mExecutorService.isShutdown()) {
-                return mExecutorService.schedule(PooledLambda.obtainRunnable(
-                        BatteryStatsImpl::updateProcStateCpuTimes,
-                        mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
-                        delayMillis, TimeUnit.MILLISECONDS);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Future<?> scheduleCopyFromAllUidsCpuTimes(
-            boolean onBattery, boolean onBatteryScreenOff) {
-        synchronized (mStats) {
-            if (!mStats.trackPerProcStateCpuTimes()) {
-                return null;
-            }
-        }
-        synchronized (BatteryExternalStatsWorker.this) {
-            if (!mExecutorService.isShutdown()) {
-                return mExecutorService.submit(PooledLambda.obtainRunnable(
-                        BatteryStatsImpl::copyFromAllUidsCpuTimes,
-                        mStats, onBattery, onBatteryScreenOff).recycleOnUse());
-            }
-        }
-        return null;
-    }
-
-    @Override
     public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
             boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
         synchronized (BatteryExternalStatsWorker.this) {
@@ -354,6 +319,25 @@
     }
 
     @Override
+    public Future<?> scheduleSyncDueToProcessStateChange(long delayMillis) {
+        synchronized (BatteryExternalStatsWorker.this) {
+            mProcessStateSync = scheduleDelayedSyncLocked(mProcessStateSync,
+                    () -> scheduleSync("procstate-change", UPDATE_ON_PROC_STATE_CHANGE),
+                    delayMillis);
+            return mProcessStateSync;
+        }
+    }
+
+    public void cancelSyncDueToProcessStateChange() {
+        synchronized (BatteryExternalStatsWorker.this) {
+            if (mProcessStateSync != null) {
+                mProcessStateSync.cancel(false);
+                mProcessStateSync = null;
+            }
+        }
+    }
+
+    @Override
     public Future<?> scheduleCleanupDueToRemovedUser(int userId) {
         synchronized (BatteryExternalStatsWorker.this) {
             return mExecutorService.schedule(() -> {
@@ -472,6 +456,9 @@
                 if ((updateFlags & UPDATE_CPU) != 0) {
                     cancelCpuSyncDueToWakelockChange();
                 }
+                if ((updateFlags & UPDATE_ON_PROC_STATE_CHANGE) == UPDATE_ON_PROC_STATE_CHANGE) {
+                    cancelSyncDueToProcessStateChange();
+                }
             }
 
             try {
@@ -491,7 +478,7 @@
                 }
 
                 if ((updateFlags & UPDATE_CPU) != 0) {
-                    mStats.copyFromAllUidsCpuTimes();
+                    mStats.updateCpuTimesForAllUids();
                 }
 
                 // Clean up any UIDs if necessary.
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 1611395..625dd63 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -352,7 +352,13 @@
                 checkTime(startTime, "getContentProviderImpl: before getProviderByClass");
                 cpr = mProviderMap.getProviderByClass(comp, userId);
                 checkTime(startTime, "getContentProviderImpl: after getProviderByClass");
-                boolean firstClass = cpr == null;
+
+                // The old stable connection's client should be killed during proc cleaning up,
+                // so do not re-use the old ContentProviderRecord, otherwise the new clients
+                // could get killed unexpectedly. Meanwhile, we should retrieve the latest
+                // application info from package manager instead of reusing the info from
+                // the dying one, as the package could have been updated.
+                boolean firstClass = cpr == null || (dyingProc == cpr.proc && dyingProc != null);
                 if (firstClass) {
                     final long ident = Binder.clearCallingIdentity();
 
@@ -381,13 +387,6 @@
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
-                } else if (dyingProc == cpr.proc && dyingProc != null) {
-                    // The old stable connection's client should be killed during proc cleaning up,
-                    // so do not re-use the old ContentProviderRecord, otherwise the new clients
-                    // could get killed unexpectedly.
-                    cpr = new ContentProviderRecord(cpr);
-                    // This is sort of "firstClass"
-                    firstClass = true;
                 }
 
                 checkTime(startTime, "getContentProviderImpl: now have ContentProviderRecord");
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index b07684c..2ec1aed 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -18,6 +18,7 @@
 
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -28,6 +29,7 @@
 import android.os.Handler;
 import android.os.Process;
 import android.os.StrictMode;
+import android.util.FeatureFlagUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -419,6 +421,10 @@
      * order of the oom adjs of their parent process.
      */
     void trimPhantomProcessesIfNecessary() {
+        if (!mService.mSystemReady || !FeatureFlagUtils.isEnabled(mService.mContext,
+                SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS)) {
+            return;
+        }
         synchronized (mService.mProcLock) {
             synchronized (mLock) {
                 mTrimPhantomProcessScheduled = false;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 80a8d63..2def50e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -371,6 +371,16 @@
     private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
 
     /**
+     * Native heap allocations in AppZygote process and its descendants will now have a
+     * non-zero tag in the most significant byte.
+     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+     * Pointers</a>
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+    private static final long NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE = 207557677;
+
+    /**
      * Enable asynchronous (ASYNC) memory tag checking in this process. This
      * flag will only have an effect on hardware supporting the ARM Memory
      * Tagging Extension (MTE).
@@ -1741,6 +1751,16 @@
         return level;
     }
 
+    private int decideTaggingLevelForAppZygote(ProcessRecord app) {
+        int level = decideTaggingLevel(app);
+        // TBI ("fake" pointer tagging) in AppZygote is controlled by a separate compat feature.
+        if (!mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING_APP_ZYGOTE, app.info)
+                && level == Zygote.MEMORY_TAG_LEVEL_TBI) {
+            level = Zygote.MEMORY_TAG_LEVEL_NONE;
+        }
+        return level;
+    }
+
     private int decideGwpAsanLevel(ProcessRecord app) {
         // Look at the process attribute first.
        if (app.processInfo != null
@@ -2271,7 +2291,8 @@
                 // not the calling one.
                 appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
                 appInfo.uid = uid;
-                appZygote = new AppZygote(appInfo, uid, firstUid, lastUid);
+                int runtimeFlags = decideTaggingLevelForAppZygote(app);
+                appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
                 mAppZygotes.put(app.info.processName, uid, appZygote);
                 zygoteProcessList = new ArrayList<ProcessRecord>();
                 mAppZygoteProcesses.put(appZygote, zygoteProcessList);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e79cba1..a7864b9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -145,9 +145,11 @@
     // giving up on them and unfreezing the screen.
     static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
 
-    // Amount of time we wait for observers to handle a user switch before we log a warning.
-    // Must be smaller than USER_SWITCH_TIMEOUT_MS.
-    private static final int USER_SWITCH_WARNING_TIMEOUT_MS = 500;
+    /**
+     * Amount of time we wait for an observer to handle a user switch before we log a warning. This
+     * wait time is per observer.
+     */
+    private static final int LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS = 500;
 
     // ActivityManager thread message constants
     static final int REPORT_USER_SWITCH_MSG = 10;
@@ -1916,6 +1918,7 @@
             final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount);
             final long dispatchStartedTime = SystemClock.elapsedRealtime();
             for (int i = 0; i < observerCount; i++) {
+                final long dispatchStartedTimeForObserver = SystemClock.elapsedRealtime();
                 try {
                     // Prepend with unique prefix to guarantee that keys are unique
                     final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i);
@@ -1926,13 +1929,20 @@
                         @Override
                         public void sendResult(Bundle data) throws RemoteException {
                             synchronized (mLock) {
-                                long delay = SystemClock.elapsedRealtime() - dispatchStartedTime;
-                                if (delay > USER_SWITCH_TIMEOUT_MS) {
-                                    Slogf.e(TAG, "User switch timeout: observer " + name
-                                            + " sent result after " + delay + " ms");
-                                } else if (delay > USER_SWITCH_WARNING_TIMEOUT_MS) {
+                                long delayForObserver = SystemClock.elapsedRealtime()
+                                        - dispatchStartedTimeForObserver;
+                                if (delayForObserver > LONG_USER_SWITCH_OBSERVER_WARNING_TIME_MS) {
                                     Slogf.w(TAG, "User switch slowed down by observer " + name
-                                            + ": result sent after " + delay + " ms");
+                                            + ": result took " + delayForObserver
+                                            + " ms to process.");
+                                }
+
+                                long totalDelay = SystemClock.elapsedRealtime()
+                                        - dispatchStartedTime;
+                                if (totalDelay > USER_SWITCH_TIMEOUT_MS) {
+                                    Slogf.e(TAG, "User switch timeout: observer " + name
+                                            + "'s result was received " + totalDelay
+                                            + " ms after dispatchUserSwitch.");
                                 }
 
                                 curWaitingUserSwitchCallbacks.remove(name);
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 456a758..fc48cd5 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -42,6 +42,7 @@
 import android.app.ActivityManager;
 import android.app.GameManager;
 import android.app.GameManager.GameMode;
+import android.app.GameState;
 import android.app.IGameManagerService;
 import android.app.compat.PackageOverride;
 import android.content.BroadcastReceiver;
@@ -51,12 +52,15 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.hardware.power.Mode;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -72,6 +76,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.CompatibilityOverrideConfig;
 import com.android.internal.compat.IPlatformCompat;
+import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.SystemService.TargetUser;
@@ -95,11 +100,14 @@
     static final int WRITE_SETTINGS = 1;
     static final int REMOVE_SETTINGS = 2;
     static final int POPULATE_GAME_MODE_SETTINGS = 3;
+    static final int SET_GAME_STATE = 4;
     static final int WRITE_SETTINGS_DELAY = 10 * 1000;  // 10 seconds
     static final PackageOverride COMPAT_ENABLED = new PackageOverride.Builder().setEnabled(true)
             .build();
     static final PackageOverride COMPAT_DISABLED = new PackageOverride.Builder().setEnabled(false)
             .build();
+    private static final String PACKAGE_NAME_MSG_KEY = "packageName";
+    private static final String USER_ID_MSG_KEY = "userId";
 
     private final Context mContext;
     private final Object mLock = new Object();
@@ -107,6 +115,7 @@
     private final Handler mHandler;
     private final PackageManager mPackageManager;
     private final IPlatformCompat mPlatformCompat;
+    private final PowerManagerInternal mPowerManagerInternal;
     private DeviceConfigListener mDeviceConfigListener;
     @GuardedBy("mLock")
     private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
@@ -123,6 +132,7 @@
         mPackageManager = mContext.getPackageManager();
         mPlatformCompat = IPlatformCompat.Stub.asInterface(
                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
     }
 
     @Override
@@ -200,6 +210,19 @@
                     updateConfigsForUser(userId, packageNames);
                     break;
                 }
+                case SET_GAME_STATE: {
+                    if (mPowerManagerInternal == null) {
+                        final Bundle data = msg.getData();
+                        Slog.d(TAG, "Error setting loading mode for package "
+                                + data.getString(PACKAGE_NAME_MSG_KEY)
+                                + " and userId " + data.getInt(USER_ID_MSG_KEY));
+                        break;
+                    }
+                    final GameState gameState = (GameState) msg.obj;
+                    final boolean isLoading = gameState.isLoading();
+                    mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+                    break;
+                }
             }
         }
     }
@@ -258,6 +281,32 @@
     }
 
     /**
+     * Called by games to communicate the current state to the platform.
+     * @param packageName The client package name.
+     * @param gameState An object set to the current state.
+     * @param userId The user associated with this state.
+     */
+    public void setGameState(String packageName, @NonNull GameState gameState, int userId) {
+        if (!isPackageGame(packageName, userId)) {
+            // Restrict to games only.
+            return;
+        }
+
+        if (getGameMode(packageName, userId) != GameManager.GAME_MODE_PERFORMANCE) {
+            // Requires performance mode to be enabled.
+            return;
+        }
+
+        final Message msg = mHandler.obtainMessage(SET_GAME_STATE);
+        final Bundle data = new Bundle();
+        data.putString(PACKAGE_NAME_MSG_KEY, packageName);
+        data.putInt(USER_ID_MSG_KEY, userId);
+        msg.setData(data);
+        msg.obj = gameState;
+        mHandler.sendMessage(msg);
+    }
+
+    /**
      * GamePackageConfiguration manages all game mode config details for its associated package.
      */
     @VisibleForTesting
@@ -385,9 +434,9 @@
             }
 
             public boolean isValid() {
-                return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
-                        || mGameMode == GameManager.GAME_MODE_BATTERY)
-                        && (!mAllowDownscale || getCompatChangeId() != 0);
+                return mGameMode == GameManager.GAME_MODE_STANDARD
+                        || mGameMode == GameManager.GAME_MODE_PERFORMANCE
+                        || mGameMode == GameManager.GAME_MODE_BATTERY;
             }
 
             /**
@@ -498,6 +547,8 @@
      */
     public static class Lifecycle extends SystemService {
         private GameManagerService mService;
+        @Nullable
+        private GameServiceController mGameServiceController;
 
         public Lifecycle(Context context) {
             super(context);
@@ -505,32 +556,49 @@
 
         @Override
         public void onStart() {
-            mService = new GameManagerService(getContext());
+            final Context context = getContext();
+            mService = new GameManagerService(context);
             publishBinderService(Context.GAME_SERVICE, mService);
             mService.registerDeviceConfigListener();
             mService.registerPackageReceiver();
+
+            if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
+                mGameServiceController = new GameServiceController(context);
+            }
         }
 
         @Override
         public void onBootPhase(int phase) {
             if (phase == PHASE_BOOT_COMPLETED) {
                 mService.onBootCompleted();
+                if (mGameServiceController != null) {
+                    mGameServiceController.onBootComplete();
+                }
             }
         }
 
         @Override
         public void onUserStarting(@NonNull TargetUser user) {
             mService.onUserStarting(user.getUserIdentifier());
+            if (mGameServiceController != null) {
+                mGameServiceController.notifyUserStarted(user);
+            }
         }
 
         @Override
         public void onUserStopping(@NonNull TargetUser user) {
             mService.onUserStopping(user.getUserIdentifier());
+            if (mGameServiceController != null) {
+                mGameServiceController.notifyUserStopped(user);
+            }
         }
 
         @Override
         public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
             mService.onUserSwitching(from, to.getUserIdentifier());
+            if (mGameServiceController != null) {
+                mGameServiceController.notifyNewForegroundUser(to);
+            }
         }
     }
 
@@ -620,6 +688,16 @@
         return getGameModeFromSettings(packageName, userId);
     }
 
+    private boolean isPackageGame(String packageName, int userId) {
+        try {
+            final ApplicationInfo applicationInfo = mPackageManager
+                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
+            return applicationInfo.category == ApplicationInfo.CATEGORY_GAME;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
     /**
      * Sets the Game Mode for the package name.
      * Verifies that the calling process has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
@@ -630,16 +708,8 @@
             throws SecurityException {
         checkPermission(Manifest.permission.MANAGE_GAME_MODE);
 
-        // Restrict to games only.
-        try {
-            final ApplicationInfo applicationInfo = mPackageManager
-                    .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId);
-            if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) {
-                // Ignore attempt to set the game mode for applications that are not identified
-                // as game. See {@link PackageManager#setApplicationCategoryHint(String, int)}
-                return;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
+        if (!isPackageGame(packageName, userId)) {
+            // Restrict to games only.
             return;
         }
 
@@ -820,7 +890,7 @@
             }
             long scaleId = modeConfig.getCompatChangeId();
             if (scaleId == 0) {
-                Slog.w(TAG, "Invalid downscaling change id " + scaleId + " for "
+                Slog.i(TAG, "Invalid downscaling change id " + scaleId + " for "
                         + packageName);
                 return;
             }
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index a0a83b1..f07d207 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -167,15 +167,9 @@
         switch (gameMode.toLowerCase()) {
             case "1":
             case "standard":
-                // Standard should only be available if other game modes are.
-                if (batteryModeSupported || perfModeSupported) {
-                    service.setGameMode(packageName, GameManager.GAME_MODE_STANDARD,
-                            userId);
-                } else {
-                    pw.println("Game mode: " + gameMode + " not supported by "
-                            + packageName);
-                    return -1;
-                }
+                // 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);
                 break;
             case "2":
             case "performance":
diff --git a/services/core/java/com/android/server/app/GameServiceConnection.java b/services/core/java/com/android/server/app/GameServiceConnection.java
new file mode 100644
index 0000000..b607789
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceConnection.java
@@ -0,0 +1,102 @@
+/*
+ * 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.app;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.games.GameService;
+import android.service.games.IGameService;
+import android.util.Slog;
+
+final class GameServiceConnection {
+    private static final String TAG = "GameServiceConnection";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final ComponentName mGameServiceComponent;
+    private final int mUser;
+    private boolean mIsBound;
+    @Nullable
+    private IGameService mGameService;
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG) {
+                Slog.d(TAG, "onServiceConnected to " + name + " for user(" + mUser + ")");
+            }
+
+            mGameService = IGameService.Stub.asInterface(service);
+            try {
+                mGameService.connected();
+            } catch (RemoteException e) {
+                Slog.w(TAG, "RemoteException while calling ready", e);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG) {
+                Slog.d(TAG, "onServiceDisconnected to " + name);
+            }
+
+            mGameService = null;
+        }
+    };
+
+    GameServiceConnection(Context context, ComponentName gameServiceComponent, int user) {
+        mContext = context;
+        mGameServiceComponent = gameServiceComponent;
+        mUser = user;
+    }
+
+    public void connect() {
+        if (mIsBound) {
+            Slog.v(TAG, "Already bound, ignoring start.");
+            return;
+        }
+
+        Intent intent = new Intent(GameService.SERVICE_INTERFACE);
+        intent.setComponent(mGameServiceComponent);
+        mIsBound = mContext.bindServiceAsUser(intent, mConnection,
+                Context.BIND_AUTO_CREATE
+                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, new UserHandle(mUser));
+        if (!mIsBound) {
+            Slog.w(TAG, "Failed binding to game service " + mGameServiceComponent);
+        }
+    }
+
+    public void disconnect() {
+        try {
+            if (mGameService != null) {
+                mGameService.disconnected();
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException in shutdown", e);
+        }
+
+        if (mIsBound) {
+            mContext.unbindService(mConnection);
+            mIsBound = false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/app/GameServiceController.java b/services/core/java/com/android/server/app/GameServiceController.java
new file mode 100644
index 0000000..d056ea9
--- /dev/null
+++ b/services/core/java/com/android/server/app/GameServiceController.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.service.games.GameService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.util.List;
+
+final class GameServiceController {
+    private static final String TAG = "GameServiceController";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    @Nullable
+    private SystemService.TargetUser mCurrentForegroundUser;
+    private boolean mHasBootCompleted;
+
+    @Nullable
+    private GameServiceConnection mGameServiceConnection;
+
+    GameServiceController(Context context) {
+        mContext = context;
+    }
+
+    void onBootComplete() {
+        mHasBootCompleted = true;
+
+        evaluateGameServiceConnection();
+    }
+
+    void notifyUserStarted(@NonNull SystemService.TargetUser user) {
+        if (mCurrentForegroundUser != null) {
+            return;
+        }
+
+        mCurrentForegroundUser = user;
+        evaluateGameServiceConnection();
+    }
+
+    void notifyNewForegroundUser(@NonNull SystemService.TargetUser user) {
+        mCurrentForegroundUser = user;
+        evaluateGameServiceConnection();
+    }
+
+    void notifyUserStopped(@NonNull SystemService.TargetUser user) {
+        if (mCurrentForegroundUser == null
+                || mCurrentForegroundUser.getUserIdentifier() != user.getUserIdentifier()) {
+            return;
+        }
+
+        mCurrentForegroundUser = null;
+        evaluateGameServiceConnection();
+    }
+
+    private void evaluateGameServiceConnection() {
+        if (!mHasBootCompleted) {
+            return;
+        }
+
+        // TODO(b/204565942): Only shutdown the existing service connection if the game service
+        // provider or user has changed.
+        if (mGameServiceConnection != null) {
+            mGameServiceConnection.disconnect();
+            mGameServiceConnection = null;
+        }
+
+        boolean isUserSupported =
+                mCurrentForegroundUser != null
+                        && mCurrentForegroundUser.isFull()
+                        && !mCurrentForegroundUser.isManagedProfile();
+        if (!isUserSupported) {
+            if (DEBUG && mCurrentForegroundUser != null) {
+                Slog.d(TAG, "User not supported: " + mCurrentForegroundUser);
+            }
+            return;
+        }
+
+        ComponentName gameServiceComponentName =
+                determineGameServiceComponentName(mCurrentForegroundUser.getUserIdentifier());
+        if (gameServiceComponentName == null) {
+            return;
+        }
+
+        mGameServiceConnection = new GameServiceConnection(
+                mContext,
+                gameServiceComponentName,
+                mCurrentForegroundUser.getUserIdentifier());
+        mGameServiceConnection.connect();
+    }
+
+    @Nullable
+    private ComponentName determineGameServiceComponentName(int userId) {
+        String gameServicePackage =
+                mContext.getResources().getString(
+                        com.android.internal.R.string.config_systemGameService);
+        if (TextUtils.isEmpty(gameServicePackage)) {
+            if (DEBUG) {
+                Slog.d(TAG, "No game service package defined");
+            }
+            return null;
+        }
+
+        List<ResolveInfo> gameServiceResolveInfos =
+                mContext.getPackageManager().queryIntentServicesAsUser(
+                        new Intent(GameService.SERVICE_INTERFACE).setPackage(gameServicePackage),
+                        PackageManager.MATCH_SYSTEM_ONLY,
+                        userId);
+
+        if (gameServiceResolveInfos.isEmpty()) {
+            Slog.v(TAG, "No available game service found for user id: " + userId);
+            return null;
+        }
+
+        for (ResolveInfo resolveInfo : gameServiceResolveInfos) {
+            if (resolveInfo.serviceInfo == null) {
+                continue;
+            }
+            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (!serviceInfo.isEnabled()) {
+                continue;
+            }
+            return serviceInfo.getComponentName();
+        }
+
+        Slog.v(TAG, "No game service found for user id: " + userId);
+        return null;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index b9cc992..8de515d 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -134,7 +134,7 @@
     private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
             + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
             + OP_PHONE_CALL_CAMERA;
-    private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
+    private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
     private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
     private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
             Duration.ofMinutes(1).toMillis();
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ff451a3..2fcdd61 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -327,7 +327,7 @@
         }
 
         boolean isBtScoRequested = isBluetoothScoRequested();
-        if (isBtScoRequested && !wasBtScoRequested) {
+        if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
             if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
                 Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for pid: "
                         + pid);
@@ -493,6 +493,12 @@
         return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
     }
 
+    /*package*/ boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
+        synchronized (mDeviceStateLock) {
+            return mDeviceInventory.isDeviceConnected(device);
+        }
+    }
+
     /*package*/ void setWiredDeviceConnectionState(int type,
             @AudioService.ConnectionState int state, String address, String name,
             String caller) {
@@ -502,6 +508,13 @@
         }
     }
 
+    /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+            @AudioService.ConnectionState int state) {
+        synchronized (mDeviceStateLock) {
+            mDeviceInventory.setTestDeviceConnectionState(device, state);
+        }
+    }
+
     /*package*/ static final class BleVolumeInfo {
         final int mIndex;
         final int mMaxIndex;
@@ -1002,7 +1015,8 @@
     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
                                                        String deviceName) {
         synchronized (mDeviceStateLock) {
-            return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+            return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName,
+                    false /*for test*/);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index f32d3b5..a27e4b77 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -224,6 +224,7 @@
         public final String mAddress;
         public final String mName;
         public final String mCaller;
+        public boolean mForTest = false;
 
         /*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
                                                String address, String name, String caller) {
@@ -521,7 +522,7 @@
             }
 
             if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
-                    wdcs.mType, wdcs.mAddress, wdcs.mName)) {
+                    wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) {
                 // change of connection state failed, bailout
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
                         .record();
@@ -593,7 +594,7 @@
     }
 
     //------------------------------------------------------------
-    //
+    // preferred device(s)
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
             @NonNull List<AudioDeviceAttributes> devices) {
@@ -674,16 +675,34 @@
         mDevRoleCapturePresetDispatchers.unregister(dispatcher);
     }
 
+    //-----------------------------------------------------------------------
+
+    /**
+     * Check if a device is in the list of connected devices
+     * @param device the device whose connection state is queried
+     * @return true if connected
+     */
+    // called with AudioDeviceBroker.mDeviceStateLock lock held
+    public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
+        final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
+                device.getAddress());
+        synchronized (mDevicesLock) {
+            return (mConnectedDevices.get(key) != null);
+        }
+    }
+
     /**
      * Implements the communication with AudioSystem to (dis)connect a device in the native layers
      * @param connect true if connection
      * @param device the device type
      * @param address the address of the device
      * @param deviceName human-readable name of device
+     * @param isForTesting if true, not calling AudioSystem for the connection as this is
+     *                    just for testing
      * @return false if an error was reported by AudioSystem
      */
     /*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
-            String deviceName) {
+            String deviceName, boolean isForTesting) {
         if (AudioService.DEBUG_DEVICES) {
             Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
                     + Integer.toHexString(device) + " address:" + address
@@ -706,9 +725,14 @@
                 Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
             }
             if (connect && !isConnected) {
-                final int res = mAudioSystem.setDeviceConnectionState(device,
-                        AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
-                        AudioSystem.AUDIO_FORMAT_DEFAULT);
+                final int res;
+                if (isForTesting) {
+                    res = AudioSystem.AUDIO_STATUS_OK;
+                } else {
+                    res = mAudioSystem.setDeviceConnectionState(device,
+                            AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+                            AudioSystem.AUDIO_FORMAT_DEFAULT);
+                }
                 if (res != AudioSystem.AUDIO_STATUS_OK) {
                     final String reason = "not connecting device 0x" + Integer.toHexString(device)
                             + " due to command error " + res;
@@ -914,6 +938,15 @@
         }
     }
 
+    /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+            @AudioService.ConnectionState int state) {
+        final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
+                device.getInternalType(), state, device.getAddress(),
+                "test device", "com.android.server.audio");
+        connection.mForTest = true;
+        onSetWiredDeviceConnectionState(connection);
+    }
+
     //-------------------------------------------------------------------
     // Internal utilities
 
diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
new file mode 100644
index 0000000..241abaf
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java
@@ -0,0 +1,165 @@
+/*
+ * 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.audio;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+
+class AudioManagerShellCommand extends ShellCommand {
+    private static final String TAG = "AudioManagerShellCommand";
+
+    private final AudioService mService;
+
+    AudioManagerShellCommand(AudioService service) {
+        mService = service;
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+        final PrintWriter pw = getOutPrintWriter();
+        switch(cmd) {
+            case "set-surround-format-enabled":
+                return setSurroundFormatEnabled();
+            case "get-is-surround-format-enabled":
+                return getIsSurroundFormatEnabled();
+            case "set-encoded-surround-mode":
+                return setEncodedSurroundMode();
+            case "get-encoded-surround-mode":
+                return getEncodedSurroundMode();
+        }
+        return 0;
+    }
+
+    @Override
+    public void onHelp() {
+        final PrintWriter pw = getOutPrintWriter();
+        pw.println("Audio manager commands:");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  set-surround-format-enabled SURROUND_FORMAT IS_ENABLED");
+        pw.println("    Enables/disabled the SURROUND_FORMAT based on IS_ENABLED");
+        pw.println("  get-is-surround-format-enabled SURROUND_FORMAT");
+        pw.println("    Returns if the SURROUND_FORMAT is enabled");
+        pw.println("  set-encoded-surround-mode SURROUND_SOUND_MODE");
+        pw.println("    Sets the encoded surround sound mode to SURROUND_SOUND_MODE");
+        pw.println("  get-encoded-surround-mode");
+        pw.println("    Returns the encoded surround sound mode");
+    }
+
+    private int setSurroundFormatEnabled() {
+        String surroundFormatText = getNextArg();
+        String isSurroundFormatEnabledText = getNextArg();
+
+        if (surroundFormatText == null) {
+            getErrPrintWriter().println("Error: no surroundFormat specified");
+            return 1;
+        }
+
+        if (isSurroundFormatEnabledText == null) {
+            getErrPrintWriter().println("Error: no enabled value for surroundFormat specified");
+            return 1;
+        }
+
+        int surroundFormat = -1;
+        boolean isSurroundFormatEnabled = false;
+        try {
+            surroundFormat = Integer.parseInt(surroundFormatText);
+            isSurroundFormatEnabled = Boolean.parseBoolean(isSurroundFormatEnabledText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for surroundFormat");
+            return 1;
+        }
+        if (surroundFormat < 0) {
+            getErrPrintWriter().println("Error: invalid value of surroundFormat");
+            return 1;
+        }
+
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setSurroundFormatEnabled(surroundFormat, isSurroundFormatEnabled);
+        return 0;
+    }
+
+    private int getIsSurroundFormatEnabled() {
+        String surroundFormatText = getNextArg();
+
+        if (surroundFormatText == null) {
+            getErrPrintWriter().println("Error: no surroundFormat specified");
+            return 1;
+        }
+
+        int surroundFormat = -1;
+        try {
+            surroundFormat = Integer.parseInt(surroundFormatText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for surroundFormat");
+            return 1;
+        }
+
+        if (surroundFormat < 0) {
+            getErrPrintWriter().println("Error: invalid value of surroundFormat");
+            return 1;
+        }
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        getOutPrintWriter().println("Value of enabled for " + surroundFormat + " is: "
+                + am.isSurroundFormatEnabled(surroundFormat));
+        return 0;
+    }
+
+    private int setEncodedSurroundMode() {
+        String encodedSurroundModeText = getNextArg();
+
+        if (encodedSurroundModeText == null) {
+            getErrPrintWriter().println("Error: no encodedSurroundMode specified");
+            return 1;
+        }
+
+        int encodedSurroundMode = -1;
+        try {
+            encodedSurroundMode = Integer.parseInt(encodedSurroundModeText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: wrong format specified for encoded surround mode");
+            return 1;
+        }
+
+        if (encodedSurroundMode < 0) {
+            getErrPrintWriter().println("Error: invalid value of encodedSurroundMode");
+            return 1;
+        }
+
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        am.setEncodedSurroundMode(encodedSurroundMode);
+        return 0;
+    }
+
+    private int getEncodedSurroundMode() {
+        final Context context = mService.mContext;
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        getOutPrintWriter().println("Encoded surround mode: " + am.getEncodedSurroundMode());
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f3b082..8615393 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -35,6 +35,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -92,6 +93,7 @@
 import android.media.IAudioService;
 import android.media.ICapturePresetDevicesRoleDispatcher;
 import android.media.ICommunicationDeviceDispatcher;
+import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
@@ -128,7 +130,9 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -1027,7 +1031,8 @@
         readUserRestrictions();
 
         mPlaybackMonitor =
-                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
+                        device -> onMuteAwaitConnectionTimeout(device));
         mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
 
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1048,6 +1053,9 @@
 
         mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false);
 
+        // monitor routing updates coming from native
+        mAudioSystem.setRoutingListener(this);
+
         // done with service initialization, continue additional work in our Handler thread
         queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
                 0 /* arg1 */,  0 /* arg2 */, null /* obj */,  0 /* delay */);
@@ -1241,26 +1249,32 @@
         initMinStreamVolumeWithoutModifyAudioSettings();
 
         updateVibratorInfos();
+
+        synchronized (mSupportedSystemUsagesLock) {
+            AudioSystem.setSupportedSystemUsages(mSupportedSystemUsages);
+        }
     }
 
     //-----------------------------------------------------------------
     // routing monitoring from AudioSystemAdapter
     @Override
     public void onRoutingUpdatedFromNative() {
-        if (!mHasSpatializerEffect) {
-            return;
-        }
         sendMsg(mAudioHandler,
                 MSG_ROUTING_UPDATED,
                 SENDMSG_REPLACE, 0, 0, null,
                 /*delay*/ 0);
     }
 
-    void monitorRoutingChanges(boolean enabled) {
-        mAudioSystem.setRoutingListener(enabled ? this : null);
+    /**
+     * called when handling MSG_ROUTING_UPDATED
+     */
+    void onRoutingUpdatedFromAudioThread() {
+        if (mHasSpatializerEffect) {
+            mSpatializerHelper.onRoutingUpdated();
+        }
+        checkMuteAwaitConnection();
     }
 
-
     //-----------------------------------------------------------------
     RoleObserver mRoleObserver;
 
@@ -1448,7 +1462,6 @@
 
         if (mHasSpatializerEffect) {
             mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
-            monitorRoutingChanges(true);
         }
 
         onIndicateSystemReady();
@@ -1985,6 +1998,18 @@
         }
     }
 
+    @Override // Binder call
+    public void onShellCommand(FileDescriptor in, FileDescriptor out,
+            FileDescriptor err, String[] args, ShellCallback callback,
+            ResultReceiver resultReceiver) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_AUDIO_POLICY)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Missing MANAGE_AUDIO_POLICY permission");
+        }
+        new AudioManagerShellCommand(AudioService.this).exec(this, in, out, err,
+                args, callback, resultReceiver);
+    }
+
     /** @see AudioManager#getSurroundFormats() */
     @Override
     public Map<Integer, Boolean> getSurroundFormats() {
@@ -3224,6 +3249,15 @@
         }
     }
 
+    private void enforceCallAudioInterceptionPermission() {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Missing CALL_AUDIO_INTERCEPTION permission");
+        }
+    }
+
+
     /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
     public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
             String callingPackage, String attributionTag) {
@@ -4943,6 +4977,26 @@
         mModeDispatchers.unregister(dispatcher);
     }
 
+    /** @see AudioManager#isPstnCallAudioInterceptable() */
+    public boolean isPstnCallAudioInterceptable() {
+        enforceCallAudioInterceptionPermission();
+
+        boolean uplinkDeviceFound = false;
+        boolean downlinkDeviceFound = false;
+        AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_ALL);
+        for (AudioDeviceInfo device : devices) {
+            if (device.getInternalType() == AudioSystem.DEVICE_OUT_TELEPHONY_TX) {
+                uplinkDeviceFound = true;
+            } else if (device.getInternalType() == AudioSystem.DEVICE_IN_TELEPHONY_RX) {
+                downlinkDeviceFound = true;
+            }
+            if (uplinkDeviceFound && downlinkDeviceFound) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** @see AudioManager#setRttEnabled() */
     @Override
     public void setRttEnabled(boolean rttEnabled) {
@@ -6324,6 +6378,20 @@
         mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
     }
 
+    /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
+    public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+            boolean connected) {
+        Objects.requireNonNull(device);
+        enforceModifyAudioRoutingPermission();
+        mDeviceBroker.setTestDeviceConnectionState(device,
+                connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
+        // simulate a routing update from native
+        sendMsg(mAudioHandler,
+                MSG_ROUTING_UPDATED,
+                SENDMSG_REPLACE, 0, 0, null,
+                /*delay*/ 0);
+    }
+
     /**
      * @hide
      * The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
@@ -7619,7 +7687,6 @@
                     mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
                     if (mHasSpatializerEffect) {
                         mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
-                        monitorRoutingChanges(true);
                     }
                     mAudioEventWakeLock.release();
                     break;
@@ -7758,7 +7825,7 @@
                     break;
 
                 case MSG_ROUTING_UPDATED:
-                    mSpatializerHelper.onRoutingUpdated();
+                    onRoutingUpdatedFromAudioThread();
                     break;
 
                 case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
@@ -8169,7 +8236,10 @@
     private void validateAudioAttributesUsage(@NonNull AudioAttributes audioAttributes) {
         @AudioAttributes.AttributeUsage int usage = audioAttributes.getSystemUsage();
         if (AudioAttributes.isSystemUsage(usage)) {
-            if (callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) {
+            if ((usage == AudioAttributes.USAGE_CALL_ASSISTANT
+                    && (audioAttributes.getAllFlags() & AudioAttributes.FLAG_CALL_REDIRECTION) != 0
+                    && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION))
+                    || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) {
                 if (!isSupportedSystemUsage(usage)) {
                     throw new IllegalArgumentException(
                             "Unsupported usage " + AudioAttributes.usageToString(usage));
@@ -8183,8 +8253,12 @@
     private boolean isValidAudioAttributesUsage(@NonNull AudioAttributes audioAttributes) {
         @AudioAttributes.AttributeUsage int usage = audioAttributes.getSystemUsage();
         if (AudioAttributes.isSystemUsage(usage)) {
-            return callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
-                    && isSupportedSystemUsage(usage);
+            return isSupportedSystemUsage(usage)
+                    && ((usage == AudioAttributes.USAGE_CALL_ASSISTANT
+                        && (audioAttributes.getAllFlags()
+                            & AudioAttributes.FLAG_CALL_REDIRECTION) != 0
+                        && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION))
+                        || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING));
         }
         return true;
     }
@@ -8192,6 +8266,9 @@
     public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName,
             String attributionTag, int flags, IAudioPolicyCallback pcb, int sdk) {
+        if ((flags & AudioManager.AUDIOFOCUS_FLAG_TEST) != 0) {
+            throw new IllegalArgumentException("Invalid test flag");
+        }
         final int uid = Binder.getCallingUid();
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")
                 .setUid(uid)
@@ -8250,7 +8327,7 @@
     /** see {@link AudioManager#requestAudioFocusForTest(AudioFocusRequest, String, int, int)} */
     public int requestAudioFocusForTest(AudioAttributes aa, int durationHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName,
-            int fakeUid, int sdk) {
+            int flags, int fakeUid, int sdk) {
         if (!enforceQueryAudioStateForTest("focus request")) {
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
@@ -8260,7 +8337,7 @@
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
         return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
-                clientId, callingPackageName, null, AudioManager.AUDIOFOCUS_FLAG_TEST,
+                clientId, callingPackageName, null, flags,
                 sdk, false /*forceDuck*/, fakeUid);
     }
 
@@ -8572,6 +8649,171 @@
     }
 
     //==========================================================================================
+    private final Object mMuteAwaitConnectionLock = new Object();
+
+    /**
+     * The device that is expected to be connected soon, and causes players to be muted until
+     * its connection, or it times out.
+     * Null when no active muting command, or it has timed out.
+     */
+    @GuardedBy("mMuteAwaitConnectionLock")
+    private AudioDeviceAttributes mMutingExpectedDevice;
+    @GuardedBy("mMuteAwaitConnectionLock")
+    private @Nullable int[] mMutedUsagesAwaitingConnection;
+
+    /** @see AudioManager#muteAwaitConnection */
+    @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+    public void muteAwaitConnection(@NonNull int[] usages,
+            @NonNull AudioDeviceAttributes device, long timeOutMs) {
+        Objects.requireNonNull(usages);
+        Objects.requireNonNull(device);
+        enforceModifyAudioRoutingPermission();
+        if (timeOutMs <= 0 || usages.length == 0) {
+            throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
+        }
+
+        if (mDeviceBroker.isDeviceConnected(device)) {
+            // not throwing an exception as there could be a race between a connection (server-side,
+            // notification of connection in flight) and a mute operation (client-side)
+            Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
+            return;
+        }
+        synchronized (mMuteAwaitConnectionLock) {
+            if (mMutingExpectedDevice != null) {
+                Log.e(TAG, "muteAwaitConnection ignored, another in progress for device:"
+                        + mMutingExpectedDevice);
+                throw new IllegalStateException("muteAwaitConnection already in progress");
+            }
+            mMutingExpectedDevice = device;
+            mMutedUsagesAwaitingConnection = usages;
+            mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+        }
+        dispatchMuteAwaitConnection(cb -> { try {
+            cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+    }
+
+    /** @see AudioManager#getMutingExpectedDevice */
+    public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+        enforceModifyAudioRoutingPermission();
+        synchronized (mMuteAwaitConnectionLock) {
+            return mMutingExpectedDevice;
+        }
+    }
+
+    /** @see AudioManager#cancelMuteAwaitConnection */
+    @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+    public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
+        Objects.requireNonNull(device);
+        enforceModifyAudioRoutingPermission();
+        Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
+        final int[] mutedUsages;
+        synchronized (mMuteAwaitConnectionLock) {
+            if (mMutingExpectedDevice == null) {
+                // not throwing an exception as there could be a race between a timeout
+                // (server-side) and a cancel operation (client-side)
+                Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
+                return;
+            }
+            if (!device.equals(mMutingExpectedDevice)) {
+                Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
+                        + "] but expected device is" + mMutingExpectedDevice);
+                throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
+            }
+            mutedUsages = mMutedUsagesAwaitingConnection;
+            mMutingExpectedDevice = null;
+            mMutedUsagesAwaitingConnection = null;
+            mPlaybackMonitor.cancelMuteAwaitConnection();
+        }
+        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
+                    AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+            } catch (RemoteException e) { } });
+    }
+
+    final RemoteCallbackList<IMuteAwaitConnectionCallback> mMuteAwaitConnectionDispatchers =
+            new RemoteCallbackList<IMuteAwaitConnectionCallback>();
+
+    /** @see AudioManager#registerMuteAwaitConnectionCallback */
+    public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
+            boolean register) {
+        enforceModifyAudioRoutingPermission();
+        if (register) {
+            mMuteAwaitConnectionDispatchers.register(cb);
+        } else {
+            mMuteAwaitConnectionDispatchers.unregister(cb);
+        }
+    }
+
+    @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+    void checkMuteAwaitConnection() {
+        final AudioDeviceAttributes device;
+        final int[] mutedUsages;
+        synchronized (mMuteAwaitConnectionLock) {
+            if (mMutingExpectedDevice == null) {
+                return;
+            }
+            device = mMutingExpectedDevice;
+            mutedUsages = mMutedUsagesAwaitingConnection;
+            if (!mDeviceBroker.isDeviceConnected(device)) {
+                return;
+            }
+            mMutingExpectedDevice = null;
+            mMutedUsagesAwaitingConnection = null;
+            Log.i(TAG, "muteAwaitConnection device " + device + " connected, unmuting");
+            mPlaybackMonitor.cancelMuteAwaitConnection();
+        }
+        dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
+                AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+            } catch (RemoteException e) { } });
+    }
+
+    /**
+     * Called by PlaybackActivityMonitor when the timeout hit for the mute on device connection
+     */
+    @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+    void onMuteAwaitConnectionTimeout(@NonNull AudioDeviceAttributes timedOutDevice) {
+        final int[] mutedUsages;
+        synchronized (mMuteAwaitConnectionLock) {
+            if (!timedOutDevice.equals(mMutingExpectedDevice)) {
+                return;
+            }
+            Log.i(TAG, "muteAwaitConnection timeout, clearing expected device "
+                    + mMutingExpectedDevice);
+            mutedUsages = mMutedUsagesAwaitingConnection;
+            mMutingExpectedDevice = null;
+            mMutedUsagesAwaitingConnection = null;
+        }
+        dispatchMuteAwaitConnection(cb -> { try {
+                cb.dispatchOnUnmutedEvent(
+                        AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
+                        timedOutDevice, mutedUsages);
+            } catch (RemoteException e) { } });
+    }
+
+    private void dispatchMuteAwaitConnection(
+            java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+        final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
+        // lazy initialization as errors unlikely
+        ArrayList<IMuteAwaitConnectionCallback> errorList = null;
+        for (int i = 0; i < nbDispatchers; i++) {
+            try {
+                callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+            } catch (Exception e) {
+                if (errorList == null) {
+                    errorList = new ArrayList<>(1);
+                }
+                errorList.add(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+            }
+        }
+        if (errorList != null) {
+            for (IMuteAwaitConnectionCallback errorItem : errorList) {
+                mMuteAwaitConnectionDispatchers.unregister(errorItem);
+            }
+        }
+        mMuteAwaitConnectionDispatchers.finishBroadcast();
+    }
+
+
+    //==========================================================================================
     // Device orientation
     //==========================================================================================
     /**
@@ -9578,6 +9820,7 @@
         boolean requireValidProjection = false;
         boolean requireCaptureAudioOrMediaOutputPerm = false;
         boolean requireModifyRouting = false;
+        boolean requireCallAudioInterception = false;
         ArrayList<AudioMix> voiceCommunicationCaptureMixes = null;
 
 
@@ -9618,7 +9861,10 @@
             // otherwise MODIFY_AUDIO_ROUTING permission is required
             if (mix.getRouteFlags() == mix.ROUTE_FLAG_LOOP_BACK_RENDER && projection != null) {
                 requireValidProjection |= true;
-            } else {
+            } else if (mix.isForCallRedirection()) {
+                requireCallAudioInterception |= true;
+            } else if (mix.containsMatchAttributeRuleForUsage(
+                            AudioAttributes.USAGE_VOICE_COMMUNICATION)) {
                 requireModifyRouting |= true;
             }
         }
@@ -9655,6 +9901,12 @@
             return false;
         }
 
+        if (requireCallAudioInterception
+                && !callerHasPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)) {
+            Log.e(TAG, "Can not capture audio without CALL_AUDIO_INTERCEPTION");
+            return false;
+        }
+
         return true;
     }
 
@@ -9904,6 +10156,27 @@
         return AudioManager.SUCCESS;
     }
 
+    /** @see AudioPolicy#getFocusStack() */
+    public List<AudioFocusInfo> getFocusStack() {
+        enforceModifyAudioRoutingPermission();
+        return mMediaFocusControl.getFocusStack();
+    }
+
+    /** @see AudioPolicy#sendFocusLoss */
+    public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser,
+            @NonNull IAudioPolicyCallback apcb) {
+        Objects.requireNonNull(focusLoser);
+        Objects.requireNonNull(apcb);
+        enforceModifyAudioRoutingPermission();
+        if (!mAudioPolicies.containsKey(apcb.asBinder())) {
+            throw new IllegalStateException("Only registered AudioPolicy can change focus");
+        }
+        if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) {
+            throw new IllegalStateException("AudioPolicy must have focus listener to change focus");
+        }
+        return mMediaFocusControl.sendFocusLoss(focusLoser);
+    }
+
     /** see AudioManager.hasRegisteredDynamicPolicy */
     public boolean hasRegisteredDynamicPolicy() {
         synchronized (mAudioPolicies) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index bf49ac8..69a4c23 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -27,6 +27,7 @@
 import android.media.AudioSystem;
 import android.media.IAudioFocusDispatcher;
 import android.media.MediaMetrics;
+import android.media.audiopolicy.AudioPolicy;
 import android.media.audiopolicy.IAudioPolicyCallback;
 import android.os.Binder;
 import android.os.Build;
@@ -221,6 +222,51 @@
         }
     }
 
+    /**
+     * Return a copy of the focus stack for external consumption (composed of AudioFocusInfo
+     * instead of FocusRequester instances)
+     * @return a SystemApi-friendly version of the focus stack, in the same order (last entry
+     *         is top of focus stack, i.e. latest focus owner)
+     * @see AudioPolicy#getFocusStack()
+     */
+    @NonNull List<AudioFocusInfo> getFocusStack() {
+        synchronized (mAudioFocusLock) {
+            final ArrayList<AudioFocusInfo> stack = new ArrayList<>(mFocusStack.size());
+            for (FocusRequester fr : mFocusStack) {
+                stack.add(fr.toAudioFocusInfo());
+            }
+            return stack;
+        }
+    }
+
+    /**
+     * Send AUDIOFOCUS_LOSS to a specific stack entry.
+     * Note this method is supporting an external API, and is restricted to LOSS in order to
+     * prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus)
+     * @param focusLoser the stack entry that is exiting the stack through a focus loss
+     * @return false if the focusLoser wasn't found in the stack, true otherwise
+     * @see AudioPolicy#sendFocusLoss(AudioFocusInfo)
+     */
+    boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) {
+        synchronized (mAudioFocusLock) {
+            FocusRequester loserToRemove = null;
+            for (FocusRequester fr : mFocusStack) {
+                if (fr.getClientId().equals(focusLoser.getClientId())) {
+                    fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
+                            false /*forceDuck*/);
+                    loserToRemove = fr;
+                    break;
+                }
+            }
+            if (loserToRemove != null) {
+                mFocusStack.remove(loserToRemove);
+                loserToRemove.release();
+                return true;
+            }
+        }
+        return false;
+    }
+
     @GuardedBy("mAudioFocusLock")
     private void notifyTopOfAudioFocusStack() {
         // notify the top of the stack it gained focus
@@ -461,13 +507,19 @@
      *                 at the top of the focus stack
      * Push the focus requester onto the audio focus stack at the first position immediately
      * following the locked focus owners.
+     * Propagate through the stack the changes that the new (future) focus owner causes.
+     * @param nfr the future focus owner that will gain focus when the locked focus owners are
+     *            removed from the stack
      * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
      *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
      */
     @GuardedBy("mAudioFocusLock")
-    private int pushBelowLockedFocusOwners(FocusRequester nfr) {
+    private int pushBelowLockedFocusOwnersAndPropagate(FocusRequester nfr) {
+        if (DEBUG) {
+            Log.v(TAG, "pushBelowLockedFocusOwnersAndPropagate client=" + nfr.getClientId());
+        }
         int lastLockedFocusOwnerIndex = mFocusStack.size();
-        for (int index = mFocusStack.size()-1; index >= 0; index--) {
+        for (int index = mFocusStack.size() - 1; index >= 0; index--) {
             if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
                 lastLockedFocusOwnerIndex = index;
             }
@@ -480,10 +532,33 @@
             propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
             mFocusStack.push(nfr);
             return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
-        } else {
-            mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
-            return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
         }
+
+        if (DEBUG) {
+            Log.v(TAG, "> lastLockedFocusOwnerIndex=" + lastLockedFocusOwnerIndex);
+        }
+        mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
+
+        // propagate potential focus loss (and removal from stack) after the newly
+        // inserted FocusRequester (at index lastLockedFocusOwnerIndex-1)
+        final List<String> clientsToRemove = new LinkedList<String>();
+        for (int index = lastLockedFocusOwnerIndex - 1; index >= 0; index--) {
+            final boolean isDefinitiveLoss =
+                    mFocusStack.elementAt(index).handleFocusLossFromGain(
+                            nfr.getGainRequest(), nfr, false /*forceDuck*/);
+            if (isDefinitiveLoss) {
+                clientsToRemove.add(mFocusStack.elementAt(index).getClientId());
+            }
+        }
+        for (String clientToRemove : clientsToRemove) {
+            if (DEBUG) {
+                Log.v(TAG, "> removing focus client " + clientToRemove);
+            }
+            removeFocusStackEntry(clientToRemove, false /*signal*/,
+                    true /*notifyFocusFollowers*/);
+        }
+
+        return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
     }
 
     /**
@@ -868,7 +943,7 @@
      * @param forceDuck only true if
      *     {@link android.media.AudioFocusRequest.Builder#setFocusGain(int)} was set to true for
      *                  accessibility.
-     * @param testUid ignored if flags is not AudioManager.AUDIOFOCUS_FLAG_TEST (strictly equals to)
+     * @param testUid ignored if flags doesn't contain AudioManager.AUDIOFOCUS_FLAG_TEST
      *                otherwise the UID being injected for testing
      * @return
      */
@@ -1029,7 +1104,7 @@
             if (focusGrantDelayed) {
                 // focusGrantDelayed being true implies we can't reassign focus right now
                 // which implies the focus stack is not empty.
-                final int requestResult = pushBelowLockedFocusOwners(nfr);
+                final int requestResult = pushBelowLockedFocusOwnersAndPropagate(nfr);
                 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                     notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
                 }
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b94cea4..b333ed2 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,9 +17,11 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioSystem;
@@ -27,21 +29,27 @@
 import android.media.PlayerBase;
 import android.media.VolumeShaper;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
 import java.text.DateFormat;
 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.Set;
+import java.util.function.Consumer;
 
 /**
  * Class to receive and dispatch updates from AudioSystem about recording configurations.
@@ -54,6 +62,7 @@
     /*package*/ static final boolean DEBUG = false;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
+    /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
 
     private static final VolumeShaper.Configuration DUCK_VSHAPE =
             new VolumeShaper.Configuration.Builder()
@@ -73,6 +82,18 @@
                     .createIfNeeded()
                     .build();
 
+    private static final long UNMUTE_DURATION_MS = 100;
+    private static final VolumeShaper.Configuration MUTE_AWAIT_CONNECTION_VSHAPE =
+            new VolumeShaper.Configuration.Builder()
+                    .setId(VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID)
+                    .setCurve(new float[] { 0.f, 1.f } /* times */,
+                            new float[] { 1.f, 0.f } /* volumes */)
+                    .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                    // even though we specify a duration, it's only used for the unmute,
+                    // for muting this volume shaper is run with PLAY_SKIP_RAMP
+                    .setDuration(UNMUTE_DURATION_MS)
+                    .build();
+
     // TODO support VolumeShaper on those players
     private static final int[] UNDUCKABLE_PLAYER_TYPES = {
             AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
@@ -90,6 +111,7 @@
     private boolean mHasPublicClients = false;
 
     private final Object mPlayerLock = new Object();
+    @GuardedBy("mPlayerLock")
     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
             new HashMap<Integer, AudioPlaybackConfiguration>();
 
@@ -97,12 +119,16 @@
     private int mSavedAlarmVolume = -1;
     private final int mMaxAlarmVolume;
     private int mPrivilegedAlarmActiveCount = 0;
+    private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
 
-    PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+    PlaybackActivityMonitor(Context context, int maxAlarmVolume,
+            Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
         mContext = context;
         mMaxAlarmVolume = maxAlarmVolume;
         PlayMonitorClient.sListenerDeathMonitor = this;
         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
+        mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
+        initEventHandler();
     }
 
     //=================================================================
@@ -170,6 +196,7 @@
         sEventLogger.log(new NewPlayerEvent(apc));
         synchronized(mPlayerLock) {
             mPlayers.put(newPiid, apc);
+            maybeMutePlayerAwaitingConnection(apc);
         }
         return newPiid;
     }
@@ -323,6 +350,7 @@
                 mPlayers.remove(new Integer(piid));
                 mDuckingManager.removeReleased(apc);
                 mFadingManager.removeReleased(apc);
+                mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid));
                 checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
                 change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
                         AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
@@ -451,7 +479,7 @@
             pw.println("\n  faded out players piids:");
             mFadingManager.dump(pw);
             // players muted due to the device ringing or being in a call
-            pw.print("\n  muted player piids:");
+            pw.print("\n  muted player piids due to call/ring:");
             for (int piid : mMutedPlayers) {
                 pw.print(" " + piid);
             }
@@ -462,6 +490,12 @@
                 pw.print(" " + uid);
             }
             pw.println("\n");
+            // muted players:
+            pw.print("\n  muted players (piids) awaiting device connection: BL3 ####");
+            for (int piid : mMutedPlayersAwaitingConnection) {
+                pw.print(" " + piid);
+            }
+            pw.println("\n");
             // log
             sEventLogger.dump(pw);
         }
@@ -1100,6 +1134,155 @@
         }
     }
 
+    private static final class MuteAwaitConnectionEvent extends AudioEventLogger.Event {
+        private final @NonNull int[] mUsagesToMute;
+
+        MuteAwaitConnectionEvent(@NonNull int[] usagesToMute) {
+            mUsagesToMute = usagesToMute;
+        }
+
+        @Override
+        public String eventToString() {
+            return "muteAwaitConnection muting usages " + Arrays.toString(mUsagesToMute);
+        }
+    }
+
     static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
             "playback activity as reported through PlayerBase");
+
+    //==========================================================================================
+    // Mute conditional on device connection
+    //==========================================================================================
+    void muteAwaitConnection(@NonNull int[] usagesToMute,
+            @NonNull AudioDeviceAttributes dev, long timeOutMs) {
+        synchronized (mPlayerLock) {
+            mutePlayersExpectingDevice(usagesToMute);
+            // schedule timeout (remove previously scheduled first)
+            mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
+            mEventHandler.sendMessageDelayed(
+                    mEventHandler.obtainMessage(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION, dev),
+                    timeOutMs);
+        }
+    }
+
+    void cancelMuteAwaitConnection() {
+        synchronized (mPlayerLock) {
+            // cancel scheduled timeout, ignore device, only one expected device at a time
+            mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
+            // unmute immediately
+            unmutePlayersExpectingDevice();
+        }
+    }
+
+    /**
+     * List of the piids of the players that are muted until a specific audio device connects
+     */
+    @GuardedBy("mPlayerLock")
+    private final ArrayList<Integer> mMutedPlayersAwaitingConnection = new ArrayList<Integer>();
+
+    /**
+     * List of AudioAttributes usages to mute until a specific audio device connects
+     */
+    @GuardedBy("mPlayerLock")
+    private @Nullable int[] mMutedUsagesAwaitingConnection = null;
+
+    @GuardedBy("mPlayerLock")
+    private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
+        sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
+        mMutedUsagesAwaitingConnection = usagesToMute;
+        final Set<Integer> piidSet = mPlayers.keySet();
+        final Iterator<Integer> piidIterator = piidSet.iterator();
+        // find which players to mute
+        while (piidIterator.hasNext()) {
+            final Integer piid = piidIterator.next();
+            final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+            if (apc == null) {
+                continue;
+            }
+            maybeMutePlayerAwaitingConnection(apc);
+        }
+    }
+
+    @GuardedBy("mPlayerLock")
+    private void maybeMutePlayerAwaitingConnection(@NonNull AudioPlaybackConfiguration apc) {
+        if (mMutedUsagesAwaitingConnection == null) {
+            return;
+        }
+        for (int usage : mMutedUsagesAwaitingConnection) {
+            if (usage == apc.getAudioAttributes().getUsage()) {
+                try {
+                    sEventLogger.log((new AudioEventLogger.StringEvent(
+                            "awaiting connection: muting piid:"
+                                    + apc.getPlayerInterfaceId()
+                                    + " uid:" + apc.getClientUid())).printLog(TAG));
+                    apc.getPlayerProxy().applyVolumeShaper(
+                            MUTE_AWAIT_CONNECTION_VSHAPE,
+                            PLAY_CREATE_IF_NEEDED);
+                    mMutedPlayersAwaitingConnection.add(apc.getPlayerInterfaceId());
+                } catch (Exception e) {
+                    Log.e(TAG, "awaiting connection: error muting player "
+                            + apc.getPlayerInterfaceId(), e);
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mPlayerLock")
+    private void unmutePlayersExpectingDevice() {
+        if (mMutedPlayersAwaitingConnection.isEmpty()) {
+            return;
+        }
+        for (int piid : mMutedPlayersAwaitingConnection) {
+            final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+            if (apc == null) {
+                continue;
+            }
+            try {
+                sEventLogger.log(new AudioEventLogger.StringEvent(
+                        "unmuting piid:" + piid).printLog(TAG));
+                apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
+                        VolumeShaper.Operation.REVERSE);
+            } catch (Exception e) {
+                Log.e(TAG, "Error unmuting player " + piid + " uid:"
+                        + apc.getClientUid(), e);
+            }
+        }
+        mMutedPlayersAwaitingConnection.clear();
+        mMutedUsagesAwaitingConnection = null;
+    }
+
+    //=================================================================
+    // Message handling
+    private Handler mEventHandler;
+    private HandlerThread mEventThread;
+
+    /**
+     * timeout for a mute awaiting a device connection
+     * args:
+     *     msg.obj: the audio device being expected
+     *         type: AudioDeviceAttributes
+     */
+    private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
+
+    private void initEventHandler() {
+        mEventThread = new HandlerThread(TAG);
+        mEventThread.start();
+        mEventHandler = new Handler(mEventThread.getLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
+                        Log.i(TAG, "Timeout for muting waiting for "
+                                + (AudioDeviceAttributes) msg.obj + ", unmuting");
+                        synchronized (mPlayerLock) {
+                            unmutePlayersExpectingDevice();
+                        }
+                        mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        };
+    }
 }
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 6a26bea..b47ea4f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -551,9 +551,9 @@
                 logd("canBeSpatialized false due to usage:" + attributes.getUsage());
                 return false;
         }
-        AudioDeviceAttributes[] devices =
-                // going through adapter to take advantage of routing cache
-                (AudioDeviceAttributes[]) mASA.getDevicesForAttributes(attributes).toArray();
+        AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
+        // going through adapter to take advantage of routing cache
+        mASA.getDevicesForAttributes(attributes).toArray(devices);
         final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
         logd("canBeSpatialized returning " + able);
         return able;
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index f42870b..758cf7a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1036,7 +1036,8 @@
         promptInfo.setAuthenticators(authenticators);
 
         return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
-                userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */);
+                userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
+                getContext());
     }
 
     /**
@@ -1375,7 +1376,8 @@
             try {
                 final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
                         mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
-                        opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists());
+                        opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
+                        getContext());
 
                 final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
 
@@ -1383,8 +1385,11 @@
                         + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
                         + " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: "
                         + promptInfo.isIgnoreEnrollmentState());
-
-                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
+                // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
+                // be shown for this case.
+                if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
+                        || preAuthStatus.second
+                        == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
                     // If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
                     // CREDENTIAL is requested and available, set the bundle to only request
                     // CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/OWNERS b/services/core/java/com/android/server/biometrics/OWNERS
index 4eac972..f05f403 100644
--- a/services/core/java/com/android/server/biometrics/OWNERS
+++ b/services/core/java/com/android/server/biometrics/OWNERS
@@ -6,3 +6,4 @@
 ilyamaty@google.com
 joshmccloskey@google.com
 jbolinger@google.com
+graciecheng@google.com
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index a5a3542..05c3f68 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -26,6 +26,8 @@
 import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.PromptInfo;
@@ -59,6 +61,7 @@
     static final int CREDENTIAL_NOT_ENROLLED = 9;
     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,
@@ -69,7 +72,8 @@
             BIOMETRIC_NOT_ENABLED_FOR_APPS,
             CREDENTIAL_NOT_ENROLLED,
             BIOMETRIC_LOCKOUT_TIMED,
-            BIOMETRIC_LOCKOUT_PERMANENT})
+            BIOMETRIC_LOCKOUT_PERMANENT,
+            BIOMETRIC_SENSOR_PRIVACY_ENABLED})
     @Retention(RetentionPolicy.SOURCE)
     @interface AuthenticatorStatus {}
 
@@ -84,13 +88,15 @@
     final boolean credentialAvailable;
     final boolean confirmationRequested;
     final boolean ignoreEnrollmentState;
+    final int userId;
+    final Context context;
 
     static PreAuthInfo create(ITrustManager trustManager,
             DevicePolicyManager devicePolicyManager,
             BiometricService.SettingObserver settingObserver,
             List<BiometricSensor> sensors,
             int userId, PromptInfo promptInfo, String opPackageName,
-            boolean checkDevicePolicyManager)
+            boolean checkDevicePolicyManager, Context context)
             throws RemoteException {
 
         final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -116,14 +122,22 @@
                         devicePolicyManager, settingObserver, sensor, userId, opPackageName,
                         checkDevicePolicyManager, requestedStrength,
                         promptInfo.getAllowedSensorIds(),
-                        promptInfo.isIgnoreEnrollmentState());
+                        promptInfo.isIgnoreEnrollmentState(),
+                        context);
 
                 Slog.d(TAG, "Package: " + opPackageName
                         + " Sensor ID: " + sensor.id
                         + " Modality: " + sensor.modality
                         + " Status: " + status);
 
-                if (status == AUTHENTICATOR_OK) {
+                // A sensor with privacy enabled will still be eligible to
+                // authenticate with biometric prompt. This is so the framework can display
+                // a sensor privacy error message to users after briefly showing the
+                // Biometric Prompt.
+                //
+                // Note: if only a certain sensor is required and the privacy is enabled,
+                // canAuthenticate() will return false.
+                if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
                     eligibleSensors.add(sensor);
                 } else {
                     ineligibleSensors.add(new Pair<>(sensor, status));
@@ -133,7 +147,7 @@
 
         return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
                 eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
-                promptInfo.isIgnoreEnrollmentState());
+                promptInfo.isIgnoreEnrollmentState(), userId, context);
     }
 
     /**
@@ -149,7 +163,7 @@
             BiometricSensor sensor, int userId, String opPackageName,
             boolean checkDevicePolicyManager, int requestedStrength,
             @NonNull List<Integer> requestedSensorIds,
-            boolean ignoreEnrollmentState) {
+            boolean ignoreEnrollmentState, Context context) {
 
         if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
             return BIOMETRIC_NO_HARDWARE;
@@ -175,6 +189,16 @@
                     && !ignoreEnrollmentState) {
                 return BIOMETRIC_NOT_ENROLLED;
             }
+            final SensorPrivacyManager sensorPrivacyManager = context
+                    .getSystemService(SensorPrivacyManager.class);
+
+            if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
+                if (sensorPrivacyManager
+                        .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+                    return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+                }
+            }
+
 
             final @LockoutTracker.LockoutMode int lockoutMode =
                     sensor.impl.getLockoutModeForUser(userId);
@@ -243,7 +267,8 @@
     private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
             boolean credentialRequested, List<BiometricSensor> eligibleSensors,
             List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
-            boolean confirmationRequested, boolean ignoreEnrollmentState) {
+            boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
+            Context context) {
         mBiometricRequested = biometricRequested;
         mBiometricStrengthRequested = biometricStrengthRequested;
         this.credentialRequested = credentialRequested;
@@ -253,6 +278,8 @@
         this.credentialAvailable = credentialAvailable;
         this.confirmationRequested = confirmationRequested;
         this.ignoreEnrollmentState = ignoreEnrollmentState;
+        this.userId = userId;
+        this.context = context;
     }
 
     private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
@@ -280,15 +307,35 @@
     private Pair<Integer, Integer> getInternalStatus() {
         @AuthenticatorStatus final int status;
         @BiometricAuthenticator.Modality int modality = TYPE_NONE;
+
+        final SensorPrivacyManager sensorPrivacyManager = context
+                .getSystemService(SensorPrivacyManager.class);
+
+        boolean cameraPrivacyEnabled = false;
+        if (sensorPrivacyManager != null) {
+            cameraPrivacyEnabled = sensorPrivacyManager
+                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+        }
+
         if (mBiometricRequested && credentialRequested) {
             if (credentialAvailable || !eligibleSensors.isEmpty()) {
-                status = AUTHENTICATOR_OK;
-                if (credentialAvailable) {
-                    modality |= TYPE_CREDENTIAL;
-                }
                 for (BiometricSensor sensor : eligibleSensors) {
                     modality |= sensor.modality;
                 }
+
+                if (credentialAvailable) {
+                    modality |= TYPE_CREDENTIAL;
+                    status = AUTHENTICATOR_OK;
+                } else if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+                    // If the only modality requested is face, credential is unavailable,
+                    // and the face sensor privacy is enabled then return
+                    // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+                    //
+                    // Note: This sensor will still be eligible for calls to authenticate.
+                    status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+                } else {
+                    status = AUTHENTICATOR_OK;
+                }
             } else {
                 // Pick the first sensor error if it exists
                 if (!ineligibleSensors.isEmpty()) {
@@ -302,10 +349,18 @@
             }
         } else if (mBiometricRequested) {
             if (!eligibleSensors.isEmpty()) {
-                 status = AUTHENTICATOR_OK;
-                 for (BiometricSensor sensor : eligibleSensors) {
-                     modality |= sensor.modality;
-                 }
+                for (BiometricSensor sensor : eligibleSensors) {
+                    modality |= sensor.modality;
+                }
+                if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+                    // If the only modality requested is face and the privacy is enabled
+                    // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+                    //
+                    // Note: This sensor will still be eligible for calls to authenticate.
+                    status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+                } else {
+                    status = AUTHENTICATOR_OK;
+                }
             } else {
                 // Pick the first sensor error if it exists
                 if (!ineligibleSensors.isEmpty()) {
@@ -326,9 +381,9 @@
             Slog.e(TAG, "No authenticators requested");
             status = BIOMETRIC_NO_HARDWARE;
         }
-
         Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality
                 + " AuthenticatorStatus: " + status);
+
         return new Pair<>(modality, status);
     }
 
@@ -362,6 +417,7 @@
             case CREDENTIAL_NOT_ENROLLED:
             case BIOMETRIC_LOCKOUT_TIMED:
             case BIOMETRIC_LOCKOUT_PERMANENT:
+            case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
                 break;
 
             case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 4f7c6b0..0e2582c 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -33,6 +33,7 @@
 import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
 import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED;
 import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
+import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED;
 import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
 
 import android.annotation.NonNull;
@@ -278,6 +279,9 @@
             case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
                 biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
                 break;
+            case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+                break;
             default:
                 Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
                 biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -337,7 +341,8 @@
 
             case BIOMETRIC_LOCKOUT_PERMANENT:
                 return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-
+            case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
+                return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
             case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
             case BIOMETRIC_HARDWARE_NOT_DETECTED:
             case BIOMETRIC_NOT_ENABLED_FOR_APPS:
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 7341e74..358263d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -503,10 +503,14 @@
     protected int getShowOverlayReason() {
         if (isKeyguard()) {
             return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
-        } else if (isSettings()) {
-            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
         } else if (isBiometricPrompt()) {
+            // BP reason always takes precedent over settings, since callers from within
+            // settings can always invoke BP.
             return BiometricOverlayConstants.REASON_AUTH_BP;
+        } else if (isSettings()) {
+            // This is pretty much only for FingerprintManager#authenticate usage from
+            // FingerprintSettings.
+            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
         } else {
             return BiometricOverlayConstants.REASON_AUTH_OTHER;
         }
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 97d791b..4131ae1 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
@@ -21,6 +21,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
@@ -56,6 +57,7 @@
     @NonNull private final LockoutCache mLockoutCache;
     @Nullable private final NotificationManager mNotificationManager;
     @Nullable private ICancellationSignal mCancellationSignal;
+    @Nullable private SensorPrivacyManager mSensorPrivacyManager;
 
     private final int[] mBiometricPromptIgnoreList;
     private final int[] mBiometricPromptIgnoreListVendor;
@@ -81,6 +83,7 @@
         mUsageStats = usageStats;
         mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -108,7 +111,16 @@
     @Override
     protected void startHalOperation() {
         try {
-            mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+            if (mSensorPrivacyManager != null
+                    && mSensorPrivacyManager
+                    .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+                    getTargetUserId())) {
+                onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                        0 /* vendorCode */);
+                mCallback.onClientFinished(this, false /* success */);
+            } else {
+                mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when requesting auth", e);
             onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 2ef0911..2158dfe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.biometrics.face.ISession;
@@ -41,6 +43,7 @@
 
     private final boolean mIsStrongBiometric;
     @Nullable private ICancellationSignal mCancellationSignal;
+    @Nullable private SensorPrivacyManager mSensorPrivacyManager;
 
     public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
             @NonNull IBinder token, long requestId,
@@ -51,6 +54,7 @@
                 BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
     }
 
     @Override
@@ -73,6 +77,14 @@
 
     @Override
     protected void startHalOperation() {
+        if (mSensorPrivacyManager != null
+                && mSensorPrivacyManager
+                .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+            onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+            mCallback.onClientFinished(this, false /* success */);
+            return;
+        }
+
         try {
             mCancellationSignal = getFreshDaemon().detectInteraction();
         } catch (RemoteException e) {
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 40f2801..7548d28 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
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFaceConstants;
@@ -55,6 +56,7 @@
     private final int[] mKeyguardIgnoreListVendor;
 
     private int mLastAcquire;
+    private SensorPrivacyManager mSensorPrivacyManager;
 
     FaceAuthenticationClient(@NonNull Context context,
             @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@@ -71,6 +73,7 @@
                 isKeyguardBypassEnabled);
         setRequestId(requestId);
         mUsageStats = usageStats;
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
 
         final Resources resources = getContext().getResources();
         mBiometricPromptIgnoreList = resources.getIntArray(
@@ -97,6 +100,15 @@
 
     @Override
     protected void startHalOperation() {
+
+        if (mSensorPrivacyManager != null
+                && mSensorPrivacyManager
+                .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+            onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+            mCallback.onClientFinished(this, false /* success */);
+            return;
+        }
+
         try {
             getFreshDaemon().authenticate(mOperationId);
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 5ce72c2..3120dc5 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -25,7 +25,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.compat.CompatChanges;
-import android.app.TaskStackListener;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.Overridable;
@@ -35,6 +34,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.hardware.CameraSessionStats;
 import android.hardware.CameraStreamStats;
@@ -84,7 +84,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -309,8 +308,6 @@
 
     private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener();
 
-    private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
-
     public static final class TaskInfo {
         public int frontTaskId;
         public boolean isResizeable;
@@ -320,54 +317,6 @@
         public int userId;
     }
 
-    private final class TaskStateHandler extends TaskStackListener {
-        private final Object mMapLock = new Object();
-
-        // maps the package name to its corresponding current top level task id
-        @GuardedBy("mMapLock")
-        private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>();
-
-        @Override
-        public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
-            synchronized (mMapLock) {
-                TaskInfo info = new TaskInfo();
-                info.frontTaskId = taskInfo.taskId;
-                info.isResizeable =
-                        (taskInfo.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE);
-                info.displayId = taskInfo.displayId;
-                info.userId = taskInfo.userId;
-                info.isFixedOrientationLandscape = ActivityInfo.isFixedOrientationLandscape(
-                        taskInfo.topActivityInfo.screenOrientation);
-                info.isFixedOrientationPortrait = ActivityInfo.isFixedOrientationPortrait(
-                        taskInfo.topActivityInfo.screenOrientation);
-                mTaskInfoMap.put(taskInfo.topActivityInfo.packageName, info);
-            }
-        }
-
-        @Override
-        public void onTaskRemoved(int taskId) {
-            synchronized (mMapLock) {
-                for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){
-                    if (entry.getValue().frontTaskId == taskId) {
-                        mTaskInfoMap.remove(entry.getKey());
-                        break;
-                    }
-                }
-            }
-        }
-
-        public @Nullable TaskInfo getFrontTaskInfo(String packageName) {
-            synchronized (mMapLock) {
-                if (mTaskInfoMap.containsKey(packageName)) {
-                    return mTaskInfoMap.get(packageName);
-                }
-            }
-
-            Log.e(TAG, "Top task with package name: " + packageName + " not found!");
-            return null;
-        }
-    }
-
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -490,18 +439,53 @@
 
     private final ICameraServiceProxy.Stub mCameraServiceProxy = new ICameraServiceProxy.Stub() {
         @Override
-        public int getRotateAndCropOverride(String packageName, int lensFacing) {
+        public int getRotateAndCropOverride(String packageName, int lensFacing, int userId) {
             if (Binder.getCallingUid() != Process.CAMERASERVER_UID) {
                 Slog.e(TAG, "Calling UID: " + Binder.getCallingUid() + " doesn't match expected " +
                         " camera service UID!");
                 return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
             }
 
+            TaskInfo taskInfo = null;
+            ParceledListSlice<ActivityManager.RecentTaskInfo> recentTasks = null;
+
+            try {
+                recentTasks = ActivityTaskManager.getService().getRecentTasks(/*maxNum*/1,
+                        /*flags*/ 0, userId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to query recent tasks!");
+                return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+            }
+
+            if ((recentTasks != null) && (!recentTasks.getList().isEmpty())) {
+                ActivityManager.RecentTaskInfo task = recentTasks.getList().get(0);
+                if (packageName.equals(task.topActivityInfo.packageName)) {
+                    taskInfo = new TaskInfo();
+                    taskInfo.frontTaskId = task.taskId;
+                    taskInfo.isResizeable =
+                            (task.topActivityInfo.resizeMode != RESIZE_MODE_UNRESIZEABLE);
+                    taskInfo.displayId = task.displayId;
+                    taskInfo.userId = task.userId;
+                    taskInfo.isFixedOrientationLandscape =
+                            ActivityInfo.isFixedOrientationLandscape(
+                                    task.topActivityInfo.screenOrientation);
+                    taskInfo.isFixedOrientationPortrait =
+                            ActivityInfo.isFixedOrientationPortrait(
+                                    task.topActivityInfo.screenOrientation);
+                } else {
+                    Log.e(TAG, "Recent task package name: " + task.topActivityInfo.packageName
+                            + " doesn't match with camera client package name: " + packageName);
+                    return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+                }
+            } else {
+                Log.e(TAG, "Recent task list is empty!");
+                return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+            }
+
             // TODO: Modify the sensor orientation in camera characteristics along with any 3A
             //  regions in capture requests/results to account for thea physical rotation. The
             //  former is somewhat tricky as it assumes that camera clients always check for the
             //  current value by retrieving the camera characteristics from the camera device.
-            TaskInfo taskInfo = mTaskStackListener.getFrontTaskInfo(packageName);
             if ((taskInfo != null) && (CompatChanges.isChangeEnabled(
                         OVERRIDE_CAMERA_ROTATE_AND_CROP_DEFAULTS, packageName,
                         UserHandle.getUserHandleForUid(taskInfo.userId)))) {
@@ -673,12 +657,6 @@
             CameraStatsJobService.schedule(mContext);
 
             try {
-                ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to register task stack listener!");
-            }
-
-            try {
                 int[] displayIds = WindowManagerGlobal.getWindowManagerService()
                         .registerDisplayWindowListener(mDisplayWindowListener);
                 for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 093ecd5..e120343 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -50,6 +50,8 @@
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IUserManager;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Parcel;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -74,6 +76,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
@@ -99,6 +103,22 @@
     private static final boolean IS_EMULATOR =
             SystemProperties.getBoolean("ro.boot.qemu", false);
 
+    @VisibleForTesting
+    public static final long DEFAULT_CLIPBOARD_TIMEOUT_MILLIS = 3600000;
+
+    /**
+     * Device config property for whether clipboard auto clear is enabled on the device
+     **/
+    public static final String PROPERTY_AUTO_CLEAR_ENABLED =
+            "auto_clear_enabled";
+
+    /**
+     * Device config property for time period in milliseconds after which clipboard is auto
+     * cleared
+     **/
+    public static final String PROPERTY_AUTO_CLEAR_TIMEOUT =
+            "auto_clear_timeout";
+
     // DeviceConfig properties
     private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
     private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
@@ -312,6 +332,10 @@
      * 'intendingUserId' and the uid is called 'intendingUid'.
      */
     private class ClipboardImpl extends IClipboard.Stub {
+
+        private final Handler mClipboardClearHandler = new ClipboardClearHandler(
+                mWorkerHandler.getLooper());
+
         @Override
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                 throws RemoteException {
@@ -352,10 +376,34 @@
             }
             checkDataOwner(clip, intendingUid);
             synchronized (mLock) {
+                scheduleAutoClear(userId);
                 setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
             }
         }
 
+        private void scheduleAutoClear(@UserIdInt int userId) {
+            final long oldIdentity = Binder.clearCallingIdentity();
+            try {
+                if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
+                        PROPERTY_AUTO_CLEAR_ENABLED, false)) {
+                    mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
+                            userId);
+                    Message clearMessage = Message.obtain(mClipboardClearHandler,
+                            ClipboardClearHandler.MSG_CLEAR, userId, 0, userId);
+                    mClipboardClearHandler.sendMessageDelayed(clearMessage,
+                            getTimeoutForAutoClear());
+                }
+            } finally {
+                Binder.restoreCallingIdentity(oldIdentity);
+            }
+        }
+
+        private long getTimeoutForAutoClear() {
+            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    PROPERTY_AUTO_CLEAR_TIMEOUT,
+                    DEFAULT_CLIPBOARD_TIMEOUT_MILLIS);
+        }
+
         @Override
         public void clearPrimaryClip(String callingPackage, @UserIdInt int userId) {
             final int intendingUid = getIntendingUid(callingPackage, userId);
@@ -365,6 +413,8 @@
                 return;
             }
             synchronized (mLock) {
+                mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
+                        userId);
                 setPrimaryClipInternalLocked(null, intendingUid, callingPackage);
             }
         }
@@ -391,6 +441,9 @@
                 PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
                 showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
+                if (clipboard.primaryClip != null) {
+                    scheduleAutoClear(userId);
+                }
                 return clipboard.primaryClip;
             }
         }
@@ -484,6 +537,32 @@
                 return null;
             }
         }
+
+        private class ClipboardClearHandler extends Handler {
+
+            public static final int MSG_CLEAR = 101;
+
+            ClipboardClearHandler(Looper looper) {
+                super(looper);
+            }
+
+            public void handleMessage(@NonNull Message msg) {
+                switch (msg.what) {
+                    case MSG_CLEAR:
+                        final int userId = msg.arg1;
+                        synchronized (mLock) {
+                            if (getClipboardLocked(userId).primaryClip != null) {
+                                FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
+                                        FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
+                                setPrimaryClipInternalLocked(null, Binder.getCallingUid(), null);
+                            }
+                        }
+                        break;
+                    default:
+                        Slog.wtf(TAG, "ClipboardClearHandler received unknown message " + msg.what);
+                }
+            }
+        }
     };
 
     @GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java
index 1196442..df95bf5 100644
--- a/services/core/java/com/android/server/communal/CommunalManagerService.java
+++ b/services/core/java/com/android/server/communal/CommunalManagerService.java
@@ -30,6 +30,7 @@
 import android.app.KeyguardManager;
 import android.app.PendingIntent;
 import android.app.communal.ICommunalManager;
+import android.app.communal.ICommunalModeListener;
 import android.app.compat.CompatChanges;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -42,6 +43,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.DreamManagerInternal;
@@ -77,13 +80,19 @@
     private final PackageReceiver mPackageReceiver;
     private final PackageManager mPackageManager;
     private final DreamManagerInternal mDreamManagerInternal;
+    private final RemoteCallbackList<ICommunalModeListener> mListeners =
+            new RemoteCallbackList<>();
 
     private final ActivityInterceptorCallback mActivityInterceptorCallback =
             new ActivityInterceptorCallback() {
                 @Nullable
                 @Override
-                public Intent intercept(ActivityInterceptorInfo info) {
+                public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                     if (!shouldIntercept(info.aInfo)) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Activity allowed, not intercepting: "
+                                    + info.aInfo.getComponentName());
+                        }
                         return null;
                     }
 
@@ -101,8 +110,10 @@
                             PendingIntent.FLAG_IMMUTABLE,
                             /* bOptions= */ null);
 
-                    return LaunchAfterAuthenticationActivity.createLaunchAfterAuthenticationIntent(
-                            new IntentSender(target));
+                    return new ActivityInterceptResult(
+                            LaunchAfterAuthenticationActivity.createLaunchAfterAuthenticationIntent(
+                                    new IntentSender(target)),
+                            info.checkedOptions);
 
                 }
             };
@@ -125,7 +136,7 @@
 
     @Override
     public void onStart() {
-        publishBinderService(Context.COMMUNAL_MANAGER_SERVICE, mBinderService);
+        publishBinderService(Context.COMMUNAL_SERVICE, mBinderService);
     }
 
     @Override
@@ -188,8 +199,17 @@
             return true;
         }
 
-        return isChangeEnabled(ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT, appInfo)
-                && getUserEnabledApps().contains(appInfo.packageName);
+        if (!isChangeEnabled(ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT, appInfo)) {
+            if (DEBUG) Slog.d(TAG, "App is not allowlisted: " + appInfo.packageName);
+            return false;
+        }
+
+        if (!getUserEnabledApps().contains(appInfo.packageName)) {
+            if (DEBUG) Slog.d(TAG, "App does not have user consent: " + appInfo.packageName);
+            return false;
+        }
+
+        return true;
     }
 
     private boolean isActiveDream(ApplicationInfo appInfo) {
@@ -219,12 +239,37 @@
         final boolean showWhenLocked =
                 (activityInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0;
         if (!showWhenLocked) {
+            if (DEBUG) {
+                Slog.d(TAG, "Activity does not contain showWhenLocked attribute: "
+                        + activityInfo.getComponentName());
+            }
             return true;
         }
 
         return !isAppAllowed(appInfo);
     }
 
+    private void dispatchCommunalMode(boolean isShowing) {
+        synchronized (mListeners) {
+            int i = mListeners.beginBroadcast();
+            while (i > 0) {
+                i--;
+                try {
+                    mListeners.getBroadcastItem(i).onCommunalModeChanged(isShowing);
+                } catch (RemoteException e) {
+                    // Handled by the RemoteCallbackList.
+                }
+            }
+            mListeners.finishBroadcast();
+        }
+    }
+
+    private void enforceReadPermission() {
+        mContext.enforceCallingPermission(Manifest.permission.READ_COMMUNAL_STATE,
+                Manifest.permission.READ_COMMUNAL_STATE
+                        + "permission required to read communal state.");
+    }
+
     private final class BinderService extends ICommunalManager.Stub {
         /**
          * Sets whether or not we are in communal mode.
@@ -235,7 +280,43 @@
             mContext.enforceCallingPermission(Manifest.permission.WRITE_COMMUNAL_STATE,
                     Manifest.permission.WRITE_COMMUNAL_STATE
                             + "permission required to modify communal state.");
+            if (mCommunalViewIsShowing.get() == isShowing) {
+                return;
+            }
             mCommunalViewIsShowing.set(isShowing);
+            dispatchCommunalMode(isShowing);
+        }
+
+        /**
+         * Checks whether or not we are in communal mode.
+         */
+        @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+        @Override
+        public boolean isCommunalMode() {
+            enforceReadPermission();
+            return mCommunalViewIsShowing.get();
+        }
+
+        /**
+         * Adds a callback to execute when communal state changes.
+         */
+        @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+        public void addCommunalModeListener(ICommunalModeListener listener) {
+            enforceReadPermission();
+            synchronized (mListeners) {
+                mListeners.register(listener);
+            }
+        }
+
+        /**
+         * Removes an added callback that execute when communal state changes.
+         */
+        @RequiresPermission(Manifest.permission.READ_COMMUNAL_STATE)
+        public void removeCommunalModeListener(ICommunalModeListener listener) {
+            enforceReadPermission();
+            synchronized (mListeners) {
+                mListeners.unregister(listener);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/communal/OWNERS b/services/core/java/com/android/server/communal/OWNERS
index e499b160..b02883d 100644
--- a/services/core/java/com/android/server/communal/OWNERS
+++ b/services/core/java/com/android/server/communal/OWNERS
@@ -1,3 +1,4 @@
 brycelee@google.com
 justinkoh@google.com
-lusilva@google.com
\ No newline at end of file
+lusilva@google.com
+xilei@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
new file mode 100644
index 0000000..39fa3f2
--- /dev/null
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -0,0 +1,64 @@
+/*
+ * 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.companion.virtual;
+
+import android.companion.virtual.IVirtualDevice;
+import android.window.DisplayWindowPolicyController;
+
+/**
+ * Virtual device manager local service interface.
+ * Only for use within system server.
+ */
+public abstract class VirtualDeviceManagerInternal {
+
+    /**
+     * Validate the virtual device.
+     */
+    public abstract boolean isValidVirtualDevice(IVirtualDevice virtualDevice);
+
+    /**
+     * Notify a virtual display is created.
+     *
+     * @param virtualDevice The virtual device where the virtual display located.
+     * @param displayId The display id of the created virtual display.
+     *
+     * @return The {@link DisplayWindowPolicyController} of the virtual device.
+     */
+    public abstract DisplayWindowPolicyController onVirtualDisplayCreated(
+            IVirtualDevice virtualDevice, int displayId);
+
+    /**
+     * Notify a virtual display is removed.
+     *
+     * @param virtualDevice The virtual device where the virtual display located.
+     * @param displayId The display id of the removed virtual display.
+     */
+    public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId);
+
+    /**
+     * Returns true if the given {@code uid} is the owner of any virtual devices that are
+     * currently active.
+     */
+    public abstract boolean isAppOwnerOfAnyVirtualDevice(int uid);
+
+    /**
+     * Returns true if the given {@code uid} is currently running on any virtual devices. This is
+     * determined by whether the app has any activities in the task stack on a virtual-device-owned
+     * display.
+     */
+    public abstract boolean isAppRunningOnAnyVirtualDevice(int uid);
+}
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 6ea84ce..ee5bda3 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -171,25 +171,28 @@
     }
 
     private NetworkMetrics getMetricsForNetwork(long timeMs, int netId) {
-        collectPendingMetricsSnapshot(timeMs);
         NetworkMetrics metrics = mNetworkMetrics.get(netId);
-        if (metrics == null) {
-            // TODO: allow to change transport for a given netid.
-            metrics = new NetworkMetrics(netId, getTransports(netId), mConnectTb);
+        final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
+        final long transports = (nc != null) ? BitUtils.packBits(nc.getTransportTypes()) : 0;
+        final boolean forceCollect =
+                (metrics != null && nc != null && metrics.transports != transports);
+        collectPendingMetricsSnapshot(timeMs, forceCollect);
+        if (metrics == null || forceCollect) {
+            metrics = new NetworkMetrics(netId, transports, mConnectTb);
             mNetworkMetrics.put(netId, metrics);
         }
         return metrics;
     }
 
     private NetworkMetricsSnapshot[] getNetworkMetricsSnapshots() {
-        collectPendingMetricsSnapshot(System.currentTimeMillis());
+        collectPendingMetricsSnapshot(System.currentTimeMillis(), false /* forceCollect */);
         return mNetworkMetricsSnapshots.toArray();
     }
 
-    private void collectPendingMetricsSnapshot(long timeMs) {
+    private void collectPendingMetricsSnapshot(long timeMs, boolean forceCollect) {
         // Detects time differences larger than the snapshot collection period.
         // This is robust against clock jumps and long inactivity periods.
-        if (Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
+        if (!forceCollect && Math.abs(timeMs - mLastSnapshot) <= METRICS_SNAPSHOT_SPAN_MS) {
             return;
         }
         mLastSnapshot = projectSnapshotTime(timeMs);
@@ -394,14 +397,6 @@
         return list;
     }
 
-    private long getTransports(int netId) {
-        final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
-        if (nc == null) {
-            return 0;
-        }
-        return BitUtils.packBits(nc.getTransportTypes());
-    }
-
     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
     static class NetworkMetricsSnapshot {
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index e9af6011..c04032f 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -87,7 +87,10 @@
     private final Sensor mLightSensor;
 
     // The mapper to translate ambient lux to screen brightness in the range [0, 1.0].
-    private final BrightnessMappingStrategy mBrightnessMapper;
+    @Nullable
+    private BrightnessMappingStrategy mCurrentBrightnessMapper;
+    private final BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
+    private final BrightnessMappingStrategy mIdleModeBrightnessMapper;
 
     // The minimum and maximum screen brightnesses.
     private final float mScreenBrightnessRangeMinimum;
@@ -215,36 +218,41 @@
     private final Injector mInjector;
 
     AutomaticBrightnessController(Callbacks callbacks, Looper looper,
-            SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
+            SensorManager sensorManager, Sensor lightSensor,
+            BrightnessMappingStrategy interactiveModeBrightnessMapper,
             int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
             HysteresisLevels screenBrightnessThresholds, Context context,
-            HighBrightnessModeController hbmController) {
-        this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
+            HighBrightnessModeController hbmController,
+            BrightnessMappingStrategy idleModeBrightnessMapper) {
+        this(new Injector(), callbacks, looper, sensorManager, lightSensor,
+                interactiveModeBrightnessMapper,
                 lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
                 lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
                 ambientBrightnessThresholds, screenBrightnessThresholds, context,
-                hbmController
+                hbmController, idleModeBrightnessMapper
         );
     }
 
     @VisibleForTesting
     AutomaticBrightnessController(Injector injector, Callbacks callbacks, Looper looper,
-            SensorManager sensorManager, Sensor lightSensor, BrightnessMappingStrategy mapper,
+            SensorManager sensorManager, Sensor lightSensor,
+            BrightnessMappingStrategy interactiveModeBrightnessMapper,
             int lightSensorWarmUpTime, float brightnessMin, float brightnessMax,
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
             HysteresisLevels screenBrightnessThresholds, Context context,
-            HighBrightnessModeController hbmController) {
+            HighBrightnessModeController hbmController,
+            BrightnessMappingStrategy idleModeBrightnessMapper) {
         mInjector = injector;
         mContext = context;
         mCallbacks = callbacks;
         mSensorManager = sensorManager;
-        mBrightnessMapper = mapper;
+        mCurrentBrightnessMapper = interactiveModeBrightnessMapper;
         mScreenBrightnessRangeMinimum = brightnessMin;
         mScreenBrightnessRangeMaximum = brightnessMax;
         mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
@@ -277,6 +285,10 @@
         mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mHbmController = hbmController;
+        mInteractiveModeBrightnessMapper = interactiveModeBrightnessMapper;
+        mIdleModeBrightnessMapper = idleModeBrightnessMapper;
+        // Initialize to active (normal) screen brightness mode
+        switchToInteractiveScreenBrightnessMode();
     }
 
     /**
@@ -291,7 +303,12 @@
         if (mLoggingEnabled == loggingEnabled) {
             return false;
         }
-        mBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        if (mInteractiveModeBrightnessMapper != null) {
+            mInteractiveModeBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        }
+        if (mIdleModeBrightnessMapper != null) {
+            mIdleModeBrightnessMapper.setLoggingEnabled(loggingEnabled);
+        }
         mLoggingEnabled = loggingEnabled;
         return true;
     }
@@ -311,7 +328,7 @@
     }
 
     public float getAutomaticScreenBrightnessAdjustment() {
-        return mBrightnessMapper.getAutoBrightnessAdjustment();
+        return mCurrentBrightnessMapper.getAutoBrightnessAdjustment();
     }
 
     public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
@@ -350,15 +367,20 @@
     }
 
     public boolean hasUserDataPoints() {
-        return mBrightnessMapper.hasUserDataPoints();
+        return mCurrentBrightnessMapper.hasUserDataPoints();
     }
 
+    // Used internally to establish whether we have deviated from the default config.
     public boolean isDefaultConfig() {
-        return mBrightnessMapper.isDefaultConfig();
+        if (isInIdleMode()) {
+            return false;
+        }
+        return mInteractiveModeBrightnessMapper.isDefaultConfig();
     }
 
+    // Called from APIs to get the configuration.
     public BrightnessConfiguration getDefaultConfig() {
-        return mBrightnessMapper.getDefaultConfig();
+        return mInteractiveModeBrightnessMapper.getDefaultConfig();
     }
 
     /**
@@ -379,7 +401,7 @@
         }
         if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) {
             mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_SHORT_TERM_MODEL,
-                    mBrightnessMapper.getShortTermModelTimeout());
+                    mCurrentBrightnessMapper.getShortTermModelTimeout());
         } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
             mHandler.removeMessages(MSG_INVALIDATE_SHORT_TERM_MODEL);
         }
@@ -398,7 +420,7 @@
             // and we can't use this data to add a new control point to the short-term model.
             return false;
         }
-        mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
+        mCurrentBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
         mShortTermModelValid = true;
         mShortTermModelAnchor = mAmbientLux;
         if (mLoggingEnabled) {
@@ -408,7 +430,7 @@
     }
 
     public void resetShortTermModel() {
-        mBrightnessMapper.clearUserDataPoints();
+        mCurrentBrightnessMapper.clearUserDataPoints();
         mShortTermModelValid = true;
         mShortTermModelAnchor = -1;
     }
@@ -421,13 +443,19 @@
     }
 
     public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) {
-        if (mBrightnessMapper.setBrightnessConfiguration(configuration)) {
-            resetShortTermModel();
+        if (mInteractiveModeBrightnessMapper.setBrightnessConfiguration(configuration)) {
+            if (!isInIdleMode()) {
+                resetShortTermModel();
+            }
             return true;
         }
         return false;
     }
 
+    public boolean isInIdleMode() {
+        return mCurrentBrightnessMapper.isForIdleMode();
+    }
+
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Automatic Brightness Controller Configuration:");
@@ -461,7 +489,12 @@
         pw.println("  mAmbientLightRingBuffer=" + mAmbientLightRingBuffer);
         pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
         pw.println("  mDisplayPolicy=" + DisplayPowerRequest.policyToString(mDisplayPolicy));
-        pw.println("  mShortTermModelTimeout=" + mBrightnessMapper.getShortTermModelTimeout());
+        pw.println("  mShortTermModelTimeout(active)="
+                + mInteractiveModeBrightnessMapper.getShortTermModelTimeout());
+        if (mIdleModeBrightnessMapper != null) {
+            pw.println("  mShortTermModelTimeout(idle)="
+                    + mIdleModeBrightnessMapper.getShortTermModelTimeout());
+        }
         pw.println("  mShortTermModelAnchor=" + mShortTermModelAnchor);
         pw.println("  mShortTermModelValid=" + mShortTermModelValid);
         pw.println("  mBrightnessAdjustmentSamplePending=" + mBrightnessAdjustmentSamplePending);
@@ -472,9 +505,15 @@
         pw.println("  mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
         pw.println("  mForegroundAppCategory=" + mForegroundAppCategory);
         pw.println("  mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
+        pw.println("  Idle mode active=" + mCurrentBrightnessMapper.isForIdleMode());
 
         pw.println();
-        mBrightnessMapper.dump(pw);
+        pw.println("  mActiveMapper=");
+        mInteractiveModeBrightnessMapper.dump(pw);
+        if (mIdleModeBrightnessMapper != null) {
+            pw.println("  mIdleMapper=");
+            mIdleModeBrightnessMapper.dump(pw);
+        }
 
         pw.println();
         mAmbientBrightnessThresholds.dump(pw);
@@ -544,7 +583,7 @@
     }
 
     private boolean setAutoBrightnessAdjustment(float adjustment) {
-        return mBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
+        return mCurrentBrightnessMapper.setAutoBrightnessAdjustment(adjustment);
     }
 
     private void setAmbientLux(float lux) {
@@ -562,7 +601,8 @@
 
         // If the short term model was invalidated and the change is drastic enough, reset it.
         if (!mShortTermModelValid && mShortTermModelAnchor != -1) {
-            if (mBrightnessMapper.shouldResetShortTermModel(mAmbientLux, mShortTermModelAnchor)) {
+            if (mCurrentBrightnessMapper.shouldResetShortTermModel(
+                    mAmbientLux, mShortTermModelAnchor)) {
                 resetShortTermModel();
             } else {
                 mShortTermModelValid = true;
@@ -743,7 +783,7 @@
             return;
         }
 
-        float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
+        float value = mCurrentBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
                 mForegroundAppCategory);
         float newScreenAutoBrightness = clampScreenBrightness(value);
 
@@ -909,6 +949,41 @@
         updateAutoBrightness(true /* sendUpdate */, false /* isManuallySet */);
     }
 
+    void switchToIdleMode() {
+        if (mIdleModeBrightnessMapper == null) {
+            return;
+        }
+        if (mCurrentBrightnessMapper.isForIdleMode()) {
+            return;
+        }
+        Slog.i(TAG, "Switching to Idle Screen Brightness Mode");
+        mCurrentBrightnessMapper = mIdleModeBrightnessMapper;
+        resetShortTermModel();
+        update();
+    }
+
+    void switchToInteractiveScreenBrightnessMode() {
+        if (!mCurrentBrightnessMapper.isForIdleMode()) {
+            return;
+        }
+        Slog.i(TAG, "Switching to Interactive Screen Brightness Mode");
+        mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
+        resetShortTermModel();
+        update();
+    }
+
+    public float convertToNits(float brightness) {
+        if (mCurrentBrightnessMapper != null) {
+            return mCurrentBrightnessMapper.convertToNits(brightness);
+        } else {
+            return -1.0f;
+        }
+    }
+
+    public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
+        mCurrentBrightnessMapper.recalculateSplines(applyAdjustment, adjustment);
+    }
+
     private final class AutomaticBrightnessHandler extends Handler {
         public AutomaticBrightnessHandler(Looper looper) {
             super(looper, null, true /*async*/);
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index beb4d5b..240168b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -135,7 +135,7 @@
             builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO);
             return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange,
-                    autoBrightnessAdjustmentMaxGamma);
+                    autoBrightnessAdjustmentMaxGamma, isForIdleMode);
         } else if (isValidMapping(luxLevels, brightnessLevelsBacklight) && !isForIdleMode) {
             return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight,
                     autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout);
@@ -355,6 +355,12 @@
     public abstract void dump(PrintWriter pw);
 
     /**
+     * We can designate a mapping strategy to be used for idle screen brightness mode.
+     * @return whether this mapping strategy is to be used for idle screen brightness mode.
+     */
+    public abstract boolean isForIdleMode();
+
+    /**
      * Check if the short term model should be reset given the anchor lux the last
      * brightness change was made at and the current ambient lux.
      */
@@ -711,6 +717,11 @@
             pw.println("  mUserBrightness=" + mUserBrightness);
         }
 
+        @Override
+        public boolean isForIdleMode() {
+            return false;
+        }
+
         private void computeSpline() {
             Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux,
                     mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma);
@@ -758,9 +769,10 @@
         private float mAutoBrightnessAdjustment;
         private float mUserLux;
         private float mUserBrightness;
+        private final boolean mIsForIdleMode;
 
         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
-                float[] brightness, float maxGamma) {
+                float[] brightness, float maxGamma, boolean isForIdleMode) {
 
             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
                     "Nits and brightness arrays must not be empty!");
@@ -772,6 +784,7 @@
             Preconditions.checkArrayElementsInRange(brightness,
                     PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness");
 
+            mIsForIdleMode = isForIdleMode;
             mMaxGamma = maxGamma;
             mAutoBrightnessAdjustment = 0;
             mUserLux = -1;
@@ -933,6 +946,11 @@
             pw.println("  mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied);
         }
 
+        @Override
+        public boolean isForIdleMode() {
+            return mIsForIdleMode;
+        }
+
         private void computeNitsBrightnessSplines(float[] nits) {
             mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness);
             mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 77ab813..c0a6abf 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -42,9 +42,13 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.compat.CompatChanges;
+import android.companion.virtual.IVirtualDevice;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
@@ -126,6 +130,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.wm.SurfaceAnimationThread;
@@ -244,9 +249,12 @@
     public final SparseArray<CallbackRecord> mCallbacks =
             new SparseArray<CallbackRecord>();
 
-    /** All {@link DisplayWindowPolicyController}s indexed by {@link DisplayInfo#displayId}. */
-    final SparseArray<DisplayWindowPolicyController> mDisplayWindowPolicyController =
-            new SparseArray<>();
+    /**
+     *  All {@link IVirtualDevice} and {@link DisplayWindowPolicyController}s indexed by
+     *  {@link DisplayInfo#displayId}.
+     */
+    final SparseArray<Pair<IVirtualDevice, DisplayWindowPolicyController>>
+            mDisplayWindowPolicyControllers = new SparseArray<>();
 
     // List of all currently registered display adapters.
     private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
@@ -416,6 +424,32 @@
     // Receives notifications about changes to Settings.
     private SettingsObserver mSettingsObserver;
 
+    // Keeps note of what state the device is in, used for idle screen brightness mode.
+    private boolean mIsDocked;
+    private boolean mIsDreaming;
+
+    private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final DisplayManagerInternal dmi =
+                    LocalServices.getService(DisplayManagerInternal.class);
+            if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+                int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                        Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                mIsDocked = dockState == Intent.EXTRA_DOCK_STATE_DESK
+                        || dockState == Intent.EXTRA_DOCK_STATE_LE_DESK
+                        || dockState == Intent.EXTRA_DOCK_STATE_HE_DESK;
+            }
+            if (Intent.ACTION_DREAMING_STARTED.equals(intent.getAction())) {
+                mIsDreaming = true;
+            } else if (Intent.ACTION_DREAMING_STOPPED.equals(intent.getAction())) {
+                mIsDreaming = false;
+            }
+            setDockedAndIdleEnabled(/* enabled= */(mIsDocked && mIsDreaming),
+                    Display.DEFAULT_DISPLAY);
+        }
+    };
+
     private final boolean mAllowNonNativeRefreshRateOverride;
 
     private final BrightnessSynchronizer mBrightnessSynchronizer;
@@ -611,6 +645,13 @@
         mSettingsObserver = new SettingsObserver();
 
         mBrightnessSynchronizer.startSynchronizing();
+
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_DREAMING_STARTED);
+        filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+        filter.addAction(Intent.ACTION_DOCK_EVENT);
+
+        mContext.registerReceiver(mIdleModeReceiver, filter);
     }
 
     @VisibleForTesting
@@ -1067,6 +1108,13 @@
                         + "setUserDisabledHdrTypesInternal");
                 return;
             }
+
+            // Verify if userDisabledHdrTypes contains expected HDR types
+            if (!isSubsetOf(Display.HdrCapabilities.HDR_TYPES, userDisabledHdrTypes)) {
+                Slog.e(TAG, "userDisabledHdrTypes contains unexpected types");
+                return;
+            }
+
             Arrays.sort(userDisabledHdrTypes);
             if (Arrays.equals(mUserDisabledHdrTypes, userDisabledHdrTypes)) {
                 return;
@@ -1089,6 +1137,15 @@
         }
     }
 
+    private boolean isSubsetOf(int[] sortedSuperset, int[] subset) {
+        for (int i : subset) {
+            if (Arrays.binarySearch(sortedSuperset, i) < 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void setAreUserDisabledHdrTypesAllowedInternal(
             boolean areUserDisabledHdrTypesAllowed) {
         synchronized (mSyncRoot) {
@@ -1180,8 +1237,8 @@
     }
 
     private int createVirtualDisplayInternal(VirtualDisplayConfig virtualDisplayConfig,
-            IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
-            DisplayWindowPolicyController controller) {
+            IVirtualDisplayCallback callback, IMediaProjection projection,
+            IVirtualDevice virtualDevice, String packageName) {
         final int callingUid = Binder.getCallingUid();
         if (!validatePackageName(callingUid, packageName)) {
             throw new SecurityException("packageName must match the calling uid");
@@ -1226,6 +1283,14 @@
             }
         }
 
+        if (virtualDevice != null) {
+            final VirtualDeviceManagerInternal vdm =
+                    getLocalService(VirtualDeviceManagerInternal.class);
+            if (!vdm.isValidVirtualDevice(virtualDevice)) {
+                throw new SecurityException("Invalid virtual device");
+            }
+        }
+
         if (callingUid != Process.SYSTEM_UID
                 && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
             if (!canProjectVideo(projection)) {
@@ -1306,8 +1371,8 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mSyncRoot) {
-                return createVirtualDisplayLocked(callback, projection, callingUid, packageName,
-                        surface, flags, virtualDisplayConfig, controller);
+                return createVirtualDisplayLocked(callback, projection, virtualDevice, callingUid,
+                        packageName, surface, flags, virtualDisplayConfig);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -1315,9 +1380,9 @@
     }
 
     private int createVirtualDisplayLocked(IVirtualDisplayCallback callback,
-            IMediaProjection projection, int callingUid, String packageName, Surface surface,
-            int flags, VirtualDisplayConfig virtualDisplayConfig,
-            DisplayWindowPolicyController controller) {
+            IMediaProjection projection, IVirtualDevice virtualDevice,
+            int callingUid, String packageName, Surface surface,
+            int flags, VirtualDisplayConfig virtualDisplayConfig) {
         if (mVirtualDisplayAdapter == null) {
             Slog.w(TAG, "Rejecting request to create private virtual display "
                     + "because the virtual display adapter is not available.");
@@ -1344,10 +1409,16 @@
 
         final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
         if (display != null) {
-            if (controller != null) {
-                mDisplayWindowPolicyController.put(display.getDisplayIdLocked(), controller);
+            final int displayId = display.getDisplayIdLocked();
+            if (virtualDevice != null) {
+                final VirtualDeviceManagerInternal vdm =
+                        getLocalService(VirtualDeviceManagerInternal.class);
+                final DisplayWindowPolicyController controller =
+                        vdm.onVirtualDisplayCreated(virtualDevice, displayId);
+                mDisplayWindowPolicyControllers.put(displayId,
+                        Pair.create(virtualDevice, controller));
             }
-            return display.getDisplayIdLocked();
+            return displayId;
         }
 
         // Something weird happened and the logical display was not created.
@@ -1391,7 +1462,13 @@
             if (device != null) {
                 final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
                 if (display != null) {
-                    mDisplayWindowPolicyController.delete(display.getDisplayIdLocked());
+                    final int displayId = display.getDisplayIdLocked();
+                    if (mDisplayWindowPolicyControllers.contains(displayId)) {
+                        Pair<IVirtualDevice, DisplayWindowPolicyController> pair =
+                                mDisplayWindowPolicyControllers.removeReturnOld(displayId);
+                        getLocalService(VirtualDeviceManagerInternal.class)
+                                .onVirtualDisplayRemoved(pair.first, displayId);
+                    }
                 }
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
@@ -2055,6 +2132,16 @@
         }
     }
 
+    void setDockedAndIdleEnabled(boolean enabled, int displayId) {
+        synchronized (mSyncRoot) {
+            final DisplayPowerController displayPowerController = mDisplayPowerControllers.get(
+                    displayId);
+            if (displayPowerController != null) {
+                displayPowerController.setAutomaticScreenBrightnessMode(enabled);
+            }
+        }
+    }
+
     private void clearViewportsLocked() {
         mViewports.clear();
     }
@@ -2345,13 +2432,13 @@
             pw.println();
             mPersistentDataStore.dump(pw);
 
-            final int displayWindowPolicyControllerCount = mDisplayWindowPolicyController.size();
+            final int displayWindowPolicyControllerCount = mDisplayWindowPolicyControllers.size();
             pw.println();
             pw.println("Display Window Policy Controllers: size="
                     + displayWindowPolicyControllerCount);
             for (int i = 0; i < displayWindowPolicyControllerCount; i++) {
-                pw.print("Display " + mDisplayWindowPolicyController.keyAt(i) + ":");
-                mDisplayWindowPolicyController.valueAt(i).dump("  ", pw);
+                pw.print("Display " + mDisplayWindowPolicyControllers.keyAt(i) + ":");
+                mDisplayWindowPolicyControllers.valueAt(i).second.dump("  ", pw);
             }
         }
         pw.println();
@@ -2922,9 +3009,10 @@
 
         @Override // Binder call
         public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
-                IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {
+                IVirtualDisplayCallback callback, IMediaProjection projection,
+                IVirtualDevice virtualDeviceToken, String packageName) {
             return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection,
-                    packageName, null /* controller */);
+                    virtualDeviceToken, packageName);
         }
 
         @Override // Binder call
@@ -3690,17 +3778,12 @@
         }
 
         @Override
-        public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
-                IVirtualDisplayCallback callback, IMediaProjection projection, String packageName,
-                DisplayWindowPolicyController controller) {
-            return createVirtualDisplayInternal(virtualDisplayConfig, callback, projection,
-                    packageName, controller);
-        }
-
-        @Override
         public DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId) {
             synchronized (mSyncRoot) {
-                return mDisplayWindowPolicyController.get(displayId);
+                if (mDisplayWindowPolicyControllers.contains(displayId)) {
+                    return mDisplayWindowPolicyControllers.get(displayId).second;
+                }
+                return null;
             }
         }
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 9412c93..9a7ddcb 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -20,9 +20,11 @@
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
 import android.os.ShellCommand;
+import android.util.Slog;
 import android.view.Display;
 
 import java.io.PrintWriter;
+import java.util.Arrays;
 
 class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
@@ -60,6 +62,24 @@
                 return setAmbientColorTemperatureOverride();
             case "constrain-launcher-metrics":
                 return setConstrainLauncherMetrics();
+            case "set-user-preferred-display-mode":
+                return setUserPreferredDisplayMode();
+            case "clear-user-preferred-display-mode":
+                return clearUserPreferredDisplayMode();
+            case "get-user-preferred-display-mode":
+                return getUserPreferredDisplayMode();
+            case "set-match-content-frame-rate-pref":
+                return setMatchContentFrameRateUserPreference();
+            case "get-match-content-frame-rate-pref":
+                return getMatchContentFrameRateUserPreference();
+            case "set-user-disabled-hdr-types":
+                return setUserDisabledHdrTypes();
+            case "get-user-disabled-hdr-types":
+                return getUserDisabledHdrTypes();
+            case "dock":
+                return setDockedAndIdle();
+            case "undock":
+                return unsetDockedAndIdle();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -93,6 +113,25 @@
         pw.println("  constrain-launcher-metrics [true|false]");
         pw.println("    Sets if Display#getRealSize and getRealMetrics should be constrained for ");
         pw.println("    Launcher.");
+        pw.println("  set-user-preferred-display-mode WIDTH HEIGHT REFRESH-RATE");
+        pw.println("    Sets the user preferred display mode which has fields WIDTH, HEIGHT and "
+                + "REFRESH-RATE");
+        pw.println("  clear-user-preferred-display-mode");
+        pw.println("    Clears the user preferred display mode");
+        pw.println("  get-user-preferred-display-mode");
+        pw.println("    Returns the user preferred display mode or null id no mode is set by user");
+        pw.println("  set-match-content-frame-rate-pref PREFERENCE");
+        pw.println("    Sets the match content frame rate preference as PREFERENCE ");
+        pw.println("  get-match-content-frame-rate-pref");
+        pw.println("    Returns the match content frame rate preference");
+        pw.println("  set-user-disabled-hdr-types TYPES...");
+        pw.println("    Sets the user disabled HDR types as TYPES");
+        pw.println("  get-user-disabled-hdr-types");
+        pw.println("    Returns the user disabled HDR types");
+        pw.println("  dock");
+        pw.println("    Sets brightness to docked + idle screen brightness mode");
+        pw.println("  undock");
+        pw.println("    Sets brightness to active (normal) screen brightness mode");
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -166,4 +205,162 @@
         mService.setShouldConstrainMetricsForLauncher(constrain);
         return 0;
     }
+
+    private int setUserPreferredDisplayMode() {
+        final String widthText = getNextArg();
+        if (widthText == null) {
+            getErrPrintWriter().println("Error: no width specified");
+            return 1;
+        }
+
+        final String heightText = getNextArg();
+        if (heightText == null) {
+            getErrPrintWriter().println("Error: no height specified");
+            return 1;
+        }
+
+        final String refreshRateText = getNextArg();
+        if (refreshRateText == null) {
+            getErrPrintWriter().println("Error: no refresh-rate specified");
+            return 1;
+        }
+
+        final int width, height;
+        final float refreshRate;
+        try {
+            width = Integer.parseInt(widthText);
+            height = Integer.parseInt(heightText);
+            refreshRate = Float.parseFloat(refreshRateText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid format of width, height or refresh rate");
+            return 1;
+        }
+        if (width < 0 || height < 0 || refreshRate <= 0.0f) {
+            getErrPrintWriter().println("Error: invalid value of width, height or refresh rate");
+            return 1;
+        }
+
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.setUserPreferredDisplayMode(new Display.Mode(width, height, refreshRate));
+        return 0;
+    }
+
+    private int clearUserPreferredDisplayMode() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.clearUserPreferredDisplayMode();
+        return 0;
+    }
+
+    private int getUserPreferredDisplayMode() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final Display.Mode mode =  dm.getUserPreferredDisplayMode();
+        if (mode == null) {
+            getOutPrintWriter().println("User preferred display mode: null");
+            return 0;
+        }
+
+        getOutPrintWriter().println("User preferred display mode: " + mode.getPhysicalWidth() + " "
+                + mode.getPhysicalHeight() + " " + mode.getRefreshRate());
+        return 0;
+    }
+
+    private int setMatchContentFrameRateUserPreference() {
+        final String matchContentFrameRatePrefText = getNextArg();
+        if (matchContentFrameRatePrefText == null) {
+            getErrPrintWriter().println("Error: no matchContentFrameRatePref specified");
+            return 1;
+        }
+
+        final int matchContentFrameRatePreference;
+        try {
+            matchContentFrameRatePreference = Integer.parseInt(matchContentFrameRatePrefText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid format of matchContentFrameRatePreference");
+            return 1;
+        }
+        if (matchContentFrameRatePreference < 0) {
+            getErrPrintWriter().println("Error: invalid value of matchContentFrameRatePreference");
+            return 1;
+        }
+
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+
+        final int refreshRateSwitchingType =
+                toRefreshRateSwitchingType(matchContentFrameRatePreference);
+        dm.setRefreshRateSwitchingType(refreshRateSwitchingType);
+        return 0;
+    }
+
+    private int getMatchContentFrameRateUserPreference() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        getOutPrintWriter().println("Match content frame rate type: "
+                + dm.getMatchContentFrameRateUserPreference());
+        return 0;
+    }
+
+    private int setUserDisabledHdrTypes() {
+        final String[] userDisabledHdrTypesText = getAllArgs();
+        if (userDisabledHdrTypesText == null) {
+            getErrPrintWriter().println("Error: no userDisabledHdrTypes specified");
+            return 1;
+        }
+
+        int[] userDisabledHdrTypes = new int[userDisabledHdrTypesText.length];
+        try {
+            int index = 0;
+            for (String userDisabledHdrType : userDisabledHdrTypesText) {
+                userDisabledHdrTypes[index++] = Integer.parseInt(userDisabledHdrType);
+            }
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid format of userDisabledHdrTypes");
+            return 1;
+        }
+
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.setUserDisabledHdrTypes(userDisabledHdrTypes);
+        return 0;
+    }
+
+    private int getUserDisabledHdrTypes() {
+        final Context context = mService.getContext();
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final int[] userDisabledHdrTypes = dm.getUserDisabledHdrTypes();
+        getOutPrintWriter().println("User disabled HDR types: "
+                + Arrays.toString(userDisabledHdrTypes));
+        return 0;
+    }
+
+    @DisplayManager.SwitchingType
+    private int toRefreshRateSwitchingType(
+            @DisplayManager.MatchContentFrameRateType int matchContentFrameRateType) {
+        switch (matchContentFrameRateType) {
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER:
+                return DisplayManager.SWITCHING_TYPE_NONE;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY:
+                return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS:
+                return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_UNKNOWN:
+            default:
+                Slog.e(TAG, matchContentFrameRateType + " is not a valid value of "
+                        + "matchContentFrameRate type.");
+                return -1;
+        }
+    }
+
+    private int setDockedAndIdle() {
+        mService.setDockedAndIdleEnabled(true, Display.DEFAULT_DISPLAY);
+        return 0;
+    }
+
+    private int unsetDockedAndIdle() {
+        mService.setDockedAndIdleEnabled(false, Display.DEFAULT_DISPLAY);
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c82b16d..efd2f6f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -41,7 +41,6 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -619,10 +618,11 @@
             mAppRequestObserver.dumpLocked(pw);
             mBrightnessObserver.dumpLocked(pw);
             mUdfpsObserver.dumpLocked(pw);
-            mSensorObserver.dumpLocked(pw);
             mHbmObserver.dumpLocked(pw);
             mSkinThermalStatusObserver.dumpLocked(pw);
         }
+
+        mSensorObserver.dump(pw);
     }
 
     private void updateVoteLocked(int priority, Vote vote) {
@@ -2244,7 +2244,7 @@
             }
         }
 
-        void dumpLocked(PrintWriter pw) {
+        void dump(PrintWriter pw) {
             pw.println("  SensorObserver");
             synchronized (mSensorObserverLock) {
                 pw.println("    mIsProxActive=" + mIsProxActive);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7f78cac..2dfaa8b 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -386,12 +386,8 @@
 
     private Sensor mLightSensor;
 
-    // The mapper between ambient lux, display backlight values, and display brightness.
-    // This mapper holds the current one that is being used. We will switch between the idle
-    // mapper and active mapper here.
-    @Nullable
-    private BrightnessMappingStrategy mCurrentBrightnessMapper;
-
+    // The mappers between ambient lux, display backlight values, and display brightness.
+    // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
     // Mapper used for active (normal) screen brightness mode
     @Nullable
     private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
@@ -610,8 +606,14 @@
     }
 
     private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
-        if (mCurrentBrightnessMapper == null) {
-            Log.w(TAG, "No brightness mapping available to recalculate splines");
+        if (mAutomaticBrightnessController == null) {
+            return;
+        }
+        if ((!mAutomaticBrightnessController.isInIdleMode()
+                && mInteractiveModeBrightnessMapper == null)
+                || (mAutomaticBrightnessController.isInIdleMode()
+                && mIdleModeBrightnessMapper == null)) {
+            Log.w(TAG, "No brightness mapping available to recalculate splines for this mode");
             return;
         }
 
@@ -619,7 +621,7 @@
         for (int i = 0; i < mNitsRange.length; i++) {
             adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
         }
-        mCurrentBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(),
+        mAutomaticBrightnessController.recalculateSplines(mCdsi.isReduceBrightColorsActivated(),
                 adjustedNits);
 
         mPendingRbcOnOrChanged = strengthChanged || justActivated;
@@ -886,9 +888,8 @@
             mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
                     mDisplayDeviceConfig);
         }
-        mCurrentBrightnessMapper = mInteractiveModeBrightnessMapper;
 
-        if (mCurrentBrightnessMapper != null) {
+        if (mInteractiveModeBrightnessMapper != null) {
             final float dozeScaleFactor = resources.getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
@@ -939,12 +940,13 @@
                 mAutomaticBrightnessController.stop();
             }
             mAutomaticBrightnessController = new AutomaticBrightnessController(this,
-                    handler.getLooper(), mSensorManager, mLightSensor, mCurrentBrightnessMapper,
-                    lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
-                    PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
-                    initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
-                    autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
-                    screenBrightnessThresholds, mContext, mHbmController);
+                    handler.getLooper(), mSensorManager, mLightSensor,
+                    mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
+                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
+                    lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
+                    darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
+                    ambientBrightnessThresholds, screenBrightnessThresholds, mContext,
+                    mHbmController, mIdleModeBrightnessMapper);
         } else {
             mUseSoftwareAutoBrightnessConfig = false;
         }
@@ -974,6 +976,16 @@
         }
     }
 
+    public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+        if (mAutomaticBrightnessController != null) {
+            if (isIdle) {
+                mAutomaticBrightnessController.switchToIdleMode();
+            } else {
+                mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
+            }
+        }
+    }
+
     private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
         @Override
         public void onAnimationStart(Animator animation) {
@@ -1422,9 +1434,14 @@
                 }
             }
 
-            if (!brightnessIsTemporary) {
+            // Report brightness to brightnesstracker:
+            // If brightness is not temporary (ie the slider has been released)
+            // AND if we are not in idle screen brightness mode.
+            if (!brightnessIsTemporary
+                    && (mAutomaticBrightnessController != null
+                            && !mAutomaticBrightnessController.isInIdleMode())) {
                 if (userInitiatedChange && (mAutomaticBrightnessController == null
-                        || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+                            || !mAutomaticBrightnessController.hasValidAmbientLux())) {
                     // If we don't have a valid lux reading we can't report a valid
                     // slider event so notify as if the system changed the brightness.
                     userInitiatedChange = false;
@@ -2162,11 +2179,10 @@
     }
 
     private float convertToNits(float brightness) {
-        if (mCurrentBrightnessMapper != null) {
-            return mCurrentBrightnessMapper.convertToNits(brightness);
-        } else {
-            return -1.0f;
+        if (mAutomaticBrightnessController == null) {
+            return -1f;
         }
+        return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
     private void updatePendingProximityRequestsLocked() {
@@ -2315,11 +2331,6 @@
         pw.println("  mReportedToPolicy=" +
                 reportedToPolicyToString(mReportedScreenStateToPolicy));
 
-        if (mIdleModeBrightnessMapper != null) {
-            pw.println("  mIdleModeBrightnessMapper= ");
-            mIdleModeBrightnessMapper.dump(pw);
-        }
-
         if (mScreenBrightnessRampAnimator != null) {
             pw.println("  mScreenBrightnessRampAnimator.isAnimating()=" +
                     mScreenBrightnessRampAnimator.isAnimating());
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 05e1bdd..c5dc23e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -39,6 +39,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.EventLog;
 import android.util.Slog;
 import android.view.IWindowManager;
 import android.view.WindowManager;
@@ -49,6 +50,7 @@
 import com.android.internal.inputmethod.InputBindResult;
 import com.android.internal.inputmethod.UnbindReason;
 import com.android.internal.view.IInputMethod;
+import com.android.server.EventLogTags;
 import com.android.server.wm.WindowManagerInternal;
 
 /**
@@ -58,6 +60,9 @@
     static final boolean DEBUG = false;
     private static final String TAG = InputMethodBindingController.class.getSimpleName();
 
+    /** Time in milliseconds that the IME service has to bind before it is reconnected. */
+    static final long TIME_TO_RECONNECT = 3 * 1000;
+
     @NonNull private final InputMethodManagerService mService;
     @NonNull private final Context mContext;
     @NonNull private final ArrayMap<String, InputMethodInfo> mMethodMap;
@@ -77,6 +82,7 @@
     private IBinder mCurToken;
     private int mCurSeq;
     private boolean mVisibleBound;
+    private boolean mSupportsStylusHw;
 
     /**
      * Binding flags for establishing connection to the {@link InputMethodService}.
@@ -239,7 +245,7 @@
     }
 
     /**
-     * Indicates whether {@link #getVisibleConnection} is currently in use.
+     * Indicates whether {@link #mVisibleConnection} is currently in use.
      */
     boolean isVisibleBound() {
         return mVisibleBound;
@@ -248,11 +254,6 @@
     /**
      * Used to bring IME service up to visible adjustment while it is being shown.
      */
-    @NonNull
-    ServiceConnection getVisibleConnection() {
-        return mVisibleConnection;
-    }
-
     private final ServiceConnection mVisibleConnection = new ServiceConnection() {
         @Override public void onBindingDied(ComponentName name) {
             synchronized (mMethodMap) {
@@ -295,6 +296,10 @@
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
                 }
+                mSupportsStylusHw = mMethodMap.get(mSelectedMethodId).supportsStylusHandwriting();
+                if (mSupportsStylusHw) {
+                    // TODO init Handwriting spy.
+                }
             }
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -334,7 +339,7 @@
                     // We consider this to be a new bind attempt, since the system
                     // should now try to restart the service for us.
                     mLastBindTime = SystemClock.uptimeMillis();
-                    mService.clearClientSessionsLocked();
+                    clearCurMethodAndSessionsLocked();
                     mService.clearInputShowRequestLocked();
                     mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
                 }
@@ -358,11 +363,12 @@
         }
 
         mCurId = null;
-        mService.clearClientSessionsLocked();
+        clearCurMethodAndSessionsLocked();
     }
 
     @GuardedBy("mMethodMap")
-    void clearCurMethodLocked() {
+    private void clearCurMethodAndSessionsLocked() {
+        mService.clearClientSessionsLocked();
         mCurMethod = null;
         mCurMethodUid = Process.INVALID_UID;
     }
@@ -382,7 +388,7 @@
 
     @GuardedBy("mMethodMap")
     @NonNull
-    InputBindResult bindCurrentMethodLocked(int displayIdToShowIme) {
+    InputBindResult bindCurrentMethodLocked() {
         InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
         if (info == null) {
             throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId);
@@ -394,7 +400,7 @@
             mCurId = info.getId();
             mLastBindTime = SystemClock.uptimeMillis();
 
-            addFreshWindowTokenLocked(displayIdToShowIme);
+            addFreshWindowTokenLocked();
             return new InputBindResult(
                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                     null, null, mCurId, mCurSeq, false);
@@ -419,7 +425,8 @@
     }
 
     @GuardedBy("mMethodMap")
-    private void addFreshWindowTokenLocked(int displayIdToShowIme) {
+    private void addFreshWindowTokenLocked() {
+        int displayIdToShowIme = mService.getDisplayIdToShowIme();
         mCurToken = new Binder();
 
         mService.setCurTokenDisplayId(displayIdToShowIme);
@@ -438,7 +445,7 @@
     }
 
     @GuardedBy("mMethodMap")
-    void unbindMainConnectionLocked() {
+    private void unbindMainConnectionLocked() {
         mContext.unbindService(mMainConnection);
         mHasConnection = false;
     }
@@ -460,17 +467,61 @@
     }
 
     @GuardedBy("mMethodMap")
-    boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
+    private boolean bindCurrentInputMethodServiceVisibleConnectionLocked() {
         mVisibleBound = bindCurrentInputMethodServiceLocked(mVisibleConnection,
                 IME_VISIBLE_BIND_FLAGS);
         return mVisibleBound;
     }
 
     @GuardedBy("mMethodMap")
-    boolean bindCurrentInputMethodServiceMainConnectionLocked() {
+    private boolean bindCurrentInputMethodServiceMainConnectionLocked() {
         mHasConnection = bindCurrentInputMethodServiceLocked(mMainConnection,
                 mImeConnectionBindFlags);
         return mHasConnection;
     }
 
+    /**
+     * Bind the IME so that it can be shown.
+     *
+     * <p>
+     * Performs a rebind if no binding is achieved in {@link #TIME_TO_RECONNECT} milliseconds.
+     */
+    @GuardedBy("mMethodMap")
+    void setCurrentMethodVisibleLocked() {
+        if (mCurMethod != null) {
+            if (DEBUG) Slog.d(TAG, "setCurrentMethodVisibleLocked: mCurToken=" + mCurToken);
+            if (mHasConnection && !mVisibleBound) {
+                bindCurrentInputMethodServiceVisibleConnectionLocked();
+            }
+            return;
+        }
+
+        long bindingDuration = SystemClock.uptimeMillis() - mLastBindTime;
+        if (mHasConnection && bindingDuration >= TIME_TO_RECONNECT) {
+            // The client has asked to have the input method shown, but
+            // we have been sitting here too long with a connection to the
+            // service and no interface received, so let's disconnect/connect
+            // to try to prod things along.
+            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
+                    bindingDuration, 1);
+            Slog.w(TAG, "Force disconnect/connect to the IME in setCurrentMethodVisibleLocked()");
+            unbindMainConnectionLocked();
+            bindCurrentInputMethodServiceMainConnectionLocked();
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
+                        + (TIME_TO_RECONNECT - bindingDuration));
+            }
+        }
+    }
+
+    /**
+     * Remove the binding needed for the IME to be shown.
+     */
+    @GuardedBy("mMethodMap")
+    void setCurrentMethodNotVisibleLocked() {
+        if (mVisibleBound) {
+            unbindVisibleConnectionLocked();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index 2328dfc..9a53d83 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -95,6 +95,21 @@
     public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
 
     /**
+     * Force enable or disable the input method associated with {@code imeId} for given user. If
+     * the input method associated with {@code imeId} is not installed, do nothing.
+     *
+     * @param imeId  The input method ID to be enabled or disabled.
+     * @param enabled {@code true} if the input method associated with {@code imeId} should be
+     *                enabled.
+     * @param userId The user ID to be queried.
+     * @return {@code true} if the input method associated with {@code imeId} was successfully
+     *         enabled or disabled, {@code false} if the input method specified is not installed
+     *         or was unable to be enabled/disabled for some other reason.
+     */
+    public abstract boolean setInputMethodEnabled(String imeId, boolean enabled,
+            @UserIdInt int userId);
+
+    /**
      * Registers a new {@link InputMethodListListener}.
      */
     public abstract void registerInputMethodListListener(InputMethodListListener listener);
@@ -168,6 +183,11 @@
                 }
 
                 @Override
+                public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
+                    return false;
+                }
+
+                @Override
                 public void registerInputMethodListListener(InputMethodListListener listener) {
                 }
 
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3c6b096..da3729d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -49,6 +49,8 @@
 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
+import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.Manifest;
@@ -111,12 +113,10 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.IndentingPrintWriter;
-import android.util.LruCache;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -250,10 +250,6 @@
 
     static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000;
 
-    static final long TIME_TO_RECONNECT = 3 * 1000;
-
-    static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
-
     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
     private static final String HANDLER_THREAD_NAME = "android.imms";
@@ -301,8 +297,6 @@
     // lock for this class.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
-    private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
-            new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
     final InputMethodSubtypeSwitchingController mSwitchingController;
 
     /**
@@ -312,13 +306,16 @@
     private int mMethodMapUpdateCount = 0;
 
     /**
-     * Indicates whether {@link InputMethodBindingController#getVisibleConnection} is currently
-     * in use.
+     * The display id for which the latest startInput was called.
      */
-    private boolean isVisibleBound() {
-        return mBindingController.isVisibleBound();
+    @GuardedBy("mMethodMap")
+    int getDisplayIdToShowIme() {
+        return mDisplayIdToShowIme;
     }
 
+    @GuardedBy("mMethodMap")
+    private int mDisplayIdToShowIme = INVALID_DISPLAY;
+
     // Ongoing notification
     private NotificationManager mNotificationManager;
     KeyguardManager mKeyguardManager;
@@ -1937,13 +1934,7 @@
         if (userId == mSettings.getCurrentUserId()) {
             return mSettings.getEnabledInputMethodListLocked();
         }
-        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                new ArrayMap<>();
-        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                methodList);
+        final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
         final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
                 mContext.getContentResolver(), methodMap, userId, true);
         return settings.getEnabledInputMethodListLocked();
@@ -2108,13 +2099,7 @@
             return mSettings.getEnabledInputMethodSubtypeListLocked(
                     mContext, imi, allowsImplicitlySelectedSubtypes);
         }
-        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                new ArrayMap<>();
-        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap,
-                methodList);
+        final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
         final InputMethodInfo imi = methodMap.get(imiId);
         if (imi == null) {
             return Collections.emptyList();
@@ -2354,10 +2339,10 @@
         }
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
-        final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
+        mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId,
                 mImeDisplayValidator);
 
-        if (displayIdToShowIme == INVALID_DISPLAY) {
+        if (mDisplayIdToShowIme == INVALID_DISPLAY) {
             mImeHiddenByDisplayPolicy = true;
             hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
@@ -2378,7 +2363,7 @@
         // Check if the input method is changing.
         // We expect the caller has already verified that the client is allowed to access this
         // display ID.
-        if (isSelectedMethodBound(displayIdToShowIme)) {
+        if (isSelectedMethodBound()) {
             if (cs.curSession != null) {
                 // Fast case: if we are already connected to the input method,
                 // then just return it.
@@ -2394,13 +2379,13 @@
 
         mBindingController.unbindCurrentMethodLocked();
 
-        return mBindingController.bindCurrentMethodLocked(displayIdToShowIme);
+        return mBindingController.bindCurrentMethodLocked();
     }
 
-    private boolean isSelectedMethodBound(int displayIdToShowIme) {
+    private boolean isSelectedMethodBound() {
         String curId = getCurId();
         return curId != null && curId.equals(getSelectedMethodId())
-                && displayIdToShowIme == mCurTokenDisplayId;
+                && mDisplayIdToShowIme == mCurTokenDisplayId;
     }
 
     @GuardedBy("mMethodMap")
@@ -2590,7 +2575,6 @@
 
             finishSessionLocked(mEnabledSession);
             mEnabledSession = null;
-            mBindingController.clearCurMethodLocked();
             scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
         }
         hideStatusBarIconLocked();
@@ -3047,42 +3031,20 @@
             return false;
         }
 
-        boolean res = false;
-        IInputMethod curMethod = getCurMethod();
-        if (curMethod != null) {
-            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + getCurToken());
+        mBindingController.setCurrentMethodVisibleLocked();
+        if (getCurMethod() != null) {
             // create a placeholder token for IMS so that IMS cannot inject windows into client app.
             Binder showInputToken = new Binder();
             mShowRequestWindowMap.put(showInputToken, windowToken);
+            IInputMethod curMethod = getCurMethod();
             executeOrSendMessage(curMethod, mCaller.obtainMessageIIOOO(MSG_SHOW_SOFT_INPUT,
                     getImeShowFlagsLocked(), reason, curMethod, resultReceiver,
                     showInputToken));
             mInputShown = true;
-            if (hasConnection() && !isVisibleBound()) {
-                mBindingController.bindCurrentInputMethodServiceVisibleConnectionLocked();
-            }
-            res = true;
-        } else {
-            long bindingDuration = SystemClock.uptimeMillis() - getLastBindTime();
-            if (hasConnection() && bindingDuration >= TIME_TO_RECONNECT) {
-                // The client has asked to have the input method shown, but
-                // we have been sitting here too long with a connection to the
-                // service and no interface received, so let's disconnect/connect
-                // to try to prod things along.
-                EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodId(),
-                        bindingDuration, 1);
-                Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
-                mBindingController.unbindMainConnectionLocked();
-                mBindingController.bindCurrentInputMethodServiceMainConnectionLocked();
-            } else {
-                if (DEBUG) {
-                    Slog.d(TAG, "Can't show input: connection = " + hasConnection() + ", time = "
-                            + (TIME_TO_RECONNECT - bindingDuration));
-                }
-            }
+            return true;
         }
 
-        return res;
+        return false;
     }
 
     @Override
@@ -3166,9 +3128,7 @@
         } else {
             res = false;
         }
-        if (hasConnection() && isVisibleBound()) {
-            mBindingController.unbindVisibleConnectionLocked();
-        }
+        mBindingController.setCurrentMethodNotVisibleLocked();
         mInputShown = false;
         mShowRequested = false;
         mShowExplicitlyRequested = false;
@@ -3522,10 +3482,6 @@
         return mWindowManagerInternal.shouldRestoreImeVisibility(windowToken);
     }
 
-    private boolean isImeVisible() {
-        return (mImeWindowVis & InputMethodService.IME_VISIBLE) != 0;
-    }
-
     @GuardedBy("mMethodMap")
     private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
         // TODO(yukawa): multi-display support.
@@ -4814,31 +4770,36 @@
         }
     }
 
+    private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+        final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+                new ArrayMap<>();
+        AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+        queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+                methodMap, methodList);
+        return methodMap;
+    }
+
     private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
         synchronized (mMethodMap) {
             if (userId == mSettings.getCurrentUserId()) {
                 if (!mMethodMap.containsKey(imeId)
                         || !mSettings.getEnabledInputMethodListLocked()
                                 .contains(mMethodMap.get(imeId))) {
-                    return false; // IME is not is found or not enabled.
+                    return false; // IME is not found or not enabled.
                 }
                 setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
                 return true;
             }
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                    new ArrayMap<>();
-            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                    methodMap, methodList);
+            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
             final InputMethodSettings settings = new InputMethodSettings(
                     mContext.getResources(), mContext.getContentResolver(), methodMap,
                     userId, false);
             if (!methodMap.containsKey(imeId)
                     || !settings.getEnabledInputMethodListLocked()
                             .contains(methodMap.get(imeId))) {
-                return false; // IME is not is found or not enabled.
+                return false; // IME is not found or not enabled.
             }
             settings.putSelectedInputMethod(imeId);
             settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
@@ -4846,6 +4807,35 @@
         }
     }
 
+    private boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) {
+        synchronized (mMethodMap) {
+            if (userId == mSettings.getCurrentUserId()) {
+                if (!mMethodMap.containsKey(imeId)) {
+                    return false; // IME is not found.
+                }
+                setInputMethodEnabledLocked(imeId, enabled);
+                return true;
+            }
+            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+            final InputMethodSettings settings = new InputMethodSettings(
+                    mContext.getResources(), mContext.getContentResolver(), methodMap,
+                    userId, false);
+            if (!methodMap.containsKey(imeId)) {
+                return false; // IME is not found.
+            }
+            if (enabled) {
+                if (!settings.getEnabledInputMethodListLocked().contains(methodMap.get(imeId))) {
+                    settings.appendAndPutEnabledInputMethodLocked(imeId, false);
+                }
+            } else {
+                settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+                        new StringBuilder(),
+                        settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId);
+            }
+            return true;
+        }
+    }
+
     private boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
             int displayId) {
         //TODO(b/150843766): Check if Input Token is valid.
@@ -4918,6 +4908,11 @@
         }
 
         @Override
+        public boolean setInputMethodEnabled(String imeId, boolean enabled, int userId) {
+            return mService.setInputMethodEnabled(imeId, enabled, userId);
+        }
+
+        @Override
         public void registerInputMethodListListener(InputMethodListListener listener) {
             mService.mInputMethodListListeners.addIfAbsent(listener);
         }
@@ -5114,7 +5109,8 @@
                     + " client=" + mCurFocusedWindowClient);
             focusedWindowClient = mCurFocusedWindowClient;
             p.println("  mCurId=" + getCurId() + " mHaveConnection=" + hasConnection()
-                    + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + isVisibleBound());
+                    + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+                    + mBindingController.isVisibleBound());
             p.println("  mCurToken=" + getCurToken());
             p.println("  mCurTokenDisplayId=" + mCurTokenDisplayId);
             p.println("  mCurHostInputToken=" + mCurHostInputToken);
@@ -5513,13 +5509,7 @@
                 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled);
             }
         } else {
-            final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-            final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
-            final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
-                    new ArrayMap<>();
-            AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
-            queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
-                    methodMap, methodList);
+            final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
             final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
                     mContext.getContentResolver(), methodMap, userId, false);
             if (enabled) {
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
new file mode 100644
index 0000000..b2bd47b
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -0,0 +1,615 @@
+/*
+ * 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.locales;
+
+import static android.os.UserHandle.USER_NULL;
+
+import static com.android.server.locales.LocaleManagerService.DEBUG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.backup.BackupManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.os.BestClock;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.HandlerThread;
+import android.os.LocaleList;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.XmlUtils;
+
+import libcore.io.IoUtils;
+
+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.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class for managing backup and restore of app-specific locales.
+ */
+class LocaleManagerBackupHelper {
+    private static final String TAG = "LocaleManagerBkpHelper"; // must be < 23 chars
+
+    // Tags and attributes for xml.
+    private static final String LOCALES_XML_TAG = "locales";
+    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 STAGE_FILE_NAME = "staged_locales";
+    private static final String SYSTEM_BACKUP_PACKAGE_KEY = "android";
+
+    private static final Pattern STAGE_FILE_NAME_PATTERN = Pattern.compile(
+            TextUtils.formatSimple("(^%s_)(\\d+)(\\.xml$)", STAGE_FILE_NAME));
+    private static final int USER_ID_GROUP_INDEX_IN_PATTERN = 2;
+    private static final Duration STAGE_FILE_RETENTION_PERIOD = Duration.ofDays(3);
+
+    private final LocaleManagerService mLocaleManagerService;
+    private final PackageManagerInternal mPackageManagerInternal;
+    private final File mStagedLocalesDir;
+    private final Clock mClock;
+    private final Context mContext;
+    private final Object mStagedDataLock = new Object();
+
+    // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using
+    // SparseArray because it is more memory-efficient than a HashMap.
+    private final SparseArray<StagedData> mStagedData = new SparseArray<>();
+
+    private final PackageMonitor mPackageMonitor;
+    private final BroadcastReceiver mUserMonitor;
+
+    LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
+            PackageManagerInternal pmInternal) {
+        this(localeManagerService.mContext, localeManagerService, pmInternal,
+                new File(Environment.getDataSystemCeDirectory(),
+                        "app_locales"), getDefaultClock());
+    }
+
+    private static @NonNull Clock getDefaultClock() {
+        return new BestClock(ZoneOffset.UTC, SystemClock.currentNetworkTimeClock(),
+                Clock.systemUTC());
+    }
+
+    @VisibleForTesting LocaleManagerBackupHelper(Context context,
+            LocaleManagerService localeManagerService,
+            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
+        mContext = context;
+        mLocaleManagerService = localeManagerService;
+        mPackageManagerInternal = pmInternal;
+        mClock = clock;
+        mStagedLocalesDir = stagedLocalesDir;
+
+        loadAllStageFiles();
+
+        HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND);
+        broadcastHandlerThread.start();
+
+        mPackageMonitor = new PackageMonitorImpl();
+        mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
+                UserHandle.ALL,
+                true);
+        mUserMonitor = new UserMonitor();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        context.registerReceiverAsUser(mUserMonitor, UserHandle.ALL, filter,
+                null, broadcastHandlerThread.getThreadHandler());
+    }
+
+    @VisibleForTesting
+    BroadcastReceiver getUserMonitor() {
+        return mUserMonitor;
+    }
+
+    @VisibleForTesting
+    PackageMonitor getPackageMonitor() {
+        return mPackageMonitor;
+    }
+
+    /**
+     * Loads the staged data into memory by reading all the files in the staged directory.
+     *
+     * <p><b>Note:</b> We don't ned to hold the lock here because this is only called in the
+     * constructor (before any broadcast receivers are registered).
+     */
+    private void loadAllStageFiles() {
+        File[] files = mStagedLocalesDir.listFiles();
+        if (files == null) {
+            return;
+        }
+        for (File file : files) {
+            String fileName = file.getName();
+            Matcher matcher = STAGE_FILE_NAME_PATTERN.matcher(fileName);
+            if (!matcher.matches()) {
+                file.delete();
+                Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
+                        "Unrecognized file"));
+                continue;
+            }
+            try {
+                final int userId = Integer.parseInt(matcher.group(USER_ID_GROUP_INDEX_IN_PATTERN));
+                StagedData stagedData = readStageFile(file);
+                if (stagedData != null) {
+                    mStagedData.put(userId, stagedData);
+                } else {
+                    file.delete();
+                    Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
+                            "Could not read file"));
+                }
+            } catch (NumberFormatException e) {
+                file.delete();
+                Slog.w(TAG, TextUtils.formatSimple("Deleted %s. Reason: %s.", fileName,
+                        "Could not parse user id from file name"));
+            }
+        }
+    }
+
+    /**
+     * Loads the stage file from the disk and parses it into a list of app backups.
+     */
+    private @Nullable StagedData readStageFile(@NonNull File file) {
+        InputStream stagedDataInputStream = null;
+        AtomicFile stageFile = new AtomicFile(file);
+        try {
+            stagedDataInputStream = stageFile.openRead();
+            final TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(stagedDataInputStream, StandardCharsets.UTF_8.name());
+
+            XmlUtils.beginDocument(parser, LOCALES_XML_TAG);
+            long creationTimeMillis = parser.getAttributeLong(/* namespace= */ null,
+                    ATTR_CREATION_TIME_MILLIS);
+            return new StagedData(creationTimeMillis, readFromXml(parser));
+        } catch (IOException | XmlPullParserException e) {
+            Slog.e(TAG, "Could not parse stage file ", e);
+        } finally {
+            IoUtils.closeQuietly(stagedDataInputStream);
+        }
+        return null;
+    }
+
+    /**
+     * @see LocaleManagerInternal#getBackupPayload(int userId)
+     */
+    public byte[] getBackupPayload(int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "getBackupPayload invoked for user id " + userId);
+        }
+
+        synchronized (mStagedDataLock) {
+            cleanStagedDataForOldEntriesLocked();
+        }
+
+        HashMap<String, String> pkgStates = new HashMap<>();
+        for (ApplicationInfo appInfo : mPackageManagerInternal.getInstalledApplications(/*flags*/0,
+                userId, Binder.getCallingUid())) {
+            try {
+                LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
+                        appInfo.packageName,
+                        userId);
+                // Backup locales 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());
+                }
+            } catch (RemoteException | IllegalArgumentException e) {
+                Slog.e(TAG, "Exception when getting locales for package: " + appInfo.packageName,
+                        e);
+            }
+        }
+
+        if (pkgStates.isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, "Final payload=null");
+            }
+            // Returning null here will ensure deletion of the entry for LMS from the backup data.
+            return null;
+        }
+
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        try {
+            // Passing arbitrary value for creationTimeMillis since it is ignored when forStage
+            // is false.
+            writeToXml(out, pkgStates, /* forStage= */ false, /* creationTimeMillis= */ -1);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not write to xml for backup ", e);
+            return null;
+        }
+
+        if (DEBUG) {
+            try {
+                Slog.d(TAG, "Final payload=" + out.toString("UTF-8"));
+            } catch (UnsupportedEncodingException e) {
+                Slog.w(TAG, "Could not encode payload to UTF-8", e);
+            }
+        }
+        return out.toByteArray();
+    }
+
+    private void cleanStagedDataForOldEntriesLocked() {
+        for (int i = 0; i < mStagedData.size(); i++) {
+            int userId = mStagedData.keyAt(i);
+            StagedData stagedData = mStagedData.get(userId);
+            if (stagedData.mCreationTimeMillis
+                    < mClock.millis() - STAGE_FILE_RETENTION_PERIOD.toMillis()) {
+                deleteStagedDataLocked(userId);
+            }
+        }
+    }
+
+    /**
+     * @see LocaleManagerInternal#stageAndApplyRestoredPayload(byte[] payload, int userId)
+     */
+    public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "stageAndApplyRestoredPayload user=" + userId + " payload="
+                    + (payload != null ? new String(payload, StandardCharsets.UTF_8) : null));
+        }
+        if (payload == null) {
+            Slog.e(TAG, "stageAndApplyRestoredPayload: no payload to restore for user " + userId);
+            return;
+        }
+
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(payload);
+
+        HashMap<String, String> pkgStates = new HashMap<>();
+        try {
+            // Parse the input blob into a list of BackupPackageState.
+            final TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+            XmlUtils.beginDocument(parser, LOCALES_XML_TAG);
+            pkgStates = readFromXml(parser);
+        } catch (IOException | XmlPullParserException e) {
+            Slog.e(TAG, "Could not parse payload ", e);
+        }
+
+        // We need a lock here to prevent race conditions when accessing the stage file.
+        // It might happen that a restore was triggered (manually using bmgr cmd) and at the same
+        // time a new package is added. We want to ensure that both these operations aren't
+        // performed simultaneously.
+        synchronized (mStagedDataLock) {
+            // Backups for apps which are yet to be installed.
+            mStagedData.put(userId, new StagedData(mClock.millis(), new HashMap<>()));
+
+            for (String pkgName : pkgStates.keySet()) {
+                String languageTags = 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);
+                } else {
+                    // Stage the data if the app isn't installed.
+                    mStagedData.get(userId).mPackageStates.put(pkgName, languageTags);
+                    if (DEBUG) {
+                        Slog.d(TAG, "Add locales=" + languageTags
+                                + " package=" + pkgName + " for lazy restore.");
+                    }
+                }
+            }
+
+            writeStageFileLocked(userId);
+        }
+    }
+
+    /**
+     * Notifies the backup manager to include the "android" package in the next backup pass.
+     */
+    public void notifyBackupManager() {
+        BackupManager.dataChanged(SYSTEM_BACKUP_PACKAGE_KEY);
+    }
+
+    private boolean isPackageInstalledForUser(String packageName, int userId) {
+        PackageInfo pkgInfo = null;
+        try {
+            pkgInfo = mContext.getPackageManager().getPackageInfoAsUser(
+                    packageName, /* flags= */ 0, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            if (DEBUG) {
+                Slog.d(TAG, "Could not get package info for " + packageName, e);
+            }
+        }
+        return pkgInfo != null;
+    }
+
+    /**
+     * Checks if locales already exist for the application and applies the restore accordingly.
+     * <p>
+     * The user might change the locales for an application before the restore is applied. In this
+     * case, we want to keep the user settings and discard the restore.
+     */
+    private void checkExistingLocalesAndApplyRestore(@NonNull String pkgName,
+            @NonNull String languageTags, int userId) {
+        try {
+            LocaleList currLocales = mLocaleManagerService.getApplicationLocales(
+                    pkgName,
+                    userId);
+            if (!currLocales.isEmpty()) {
+                return;
+            }
+        } catch (RemoteException 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));
+            if (DEBUG) {
+                Slog.d(TAG, "Restored locales=" + languageTags + " for package=" + pkgName);
+            }
+        } catch (RemoteException | IllegalArgumentException e) {
+            Slog.e(TAG, "Could not restore locales for " + pkgName, e);
+        }
+    }
+
+    /**
+     * Converts the list of app backups into xml and writes it onto the disk.
+     */
+    private void writeStageFileLocked(int userId) {
+        StagedData stagedData = mStagedData.get(userId);
+        if (stagedData.mPackageStates.isEmpty()) {
+            deleteStagedDataLocked(userId);
+            return;
+        }
+
+        final FileOutputStream stagedDataOutputStream;
+        AtomicFile stageFile = new AtomicFile(
+                new File(mStagedLocalesDir,
+                        TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId)));
+        try {
+            stagedDataOutputStream = stageFile.startWrite();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to save stage file");
+            return;
+        }
+
+        try {
+            writeToXml(stagedDataOutputStream, stagedData.mPackageStates,  /* forStage= */ true,
+                    stagedData.mCreationTimeMillis);
+            stageFile.finishWrite(stagedDataOutputStream);
+            if (DEBUG) {
+                Slog.d(TAG, "Stage file written.");
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not write stage file", e);
+            stageFile.failWrite(stagedDataOutputStream);
+        }
+    }
+
+    private void deleteStagedDataLocked(@UserIdInt int userId) {
+        AtomicFile stageFile = getStageFileIfExistsLocked(userId);
+        if (stageFile != null) {
+            stageFile.delete();
+        }
+        mStagedData.remove(userId);
+    }
+
+    private @Nullable AtomicFile getStageFileIfExistsLocked(@UserIdInt int userId) {
+        final File stageFile = new File(mStagedLocalesDir,
+                TextUtils.formatSimple("%s_%d.xml", STAGE_FILE_NAME, userId));
+        return stageFile.isFile() ? new AtomicFile(stageFile)
+                : null;
+    }
+
+    /**
+     * Parses the backup data from the serialized xml input stream.
+     */
+    private @NonNull HashMap<String, String> readFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        HashMap<String, String> 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);
+
+                if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(languageTags)) {
+                    packageStates.put(packageName, languageTags);
+                }
+            }
+        }
+        return packageStates;
+    }
+
+    /**
+     * Converts the list of app backup data into a serialized xml stream.
+     *
+     * @param forStage Flag to indicate whether this method is called for the purpose of
+     * staging the data. Note that if this is false, {@code creationTimeMillis} is ignored because
+     * we only need it for the stage data.
+     * @param creationTimeMillis The timestamp when the stage data was created. This is required
+     * to determine when to delete the stage data.
+     */
+    private static void writeToXml(OutputStream stream,
+            @NonNull HashMap<String, String> pkgStates, boolean forStage, long creationTimeMillis)
+            throws IOException {
+        if (pkgStates.isEmpty()) {
+            // No need to write anything at all if pkgStates is empty.
+            return;
+        }
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(stream, StandardCharsets.UTF_8.name());
+        out.startDocument(/* encoding= */ null, /* standalone= */ true);
+        out.startTag(/* namespace= */ null, LOCALES_XML_TAG);
+
+        if (forStage) {
+            out.attribute(/* namespace= */ null, ATTR_CREATION_TIME_MILLIS,
+                    Long.toString(creationTimeMillis));
+        }
+
+        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.endTag(/*namespace= */ null, PACKAGE_XML_TAG);
+        }
+
+        out.endTag(/* namespace= */ null, LOCALES_XML_TAG);
+        out.endDocument();
+    }
+
+    private static class StagedData {
+        final long mCreationTimeMillis;
+        final HashMap<String, String> mPackageStates;
+
+        StagedData(long creationTimeMillis, HashMap<String, String> pkgStates) {
+            mCreationTimeMillis = creationTimeMillis;
+            mPackageStates = pkgStates;
+        }
+    }
+
+    /**
+     * Broadcast listener to capture user removed event.
+     *
+     * <p>The stage file is deleted when a user is removed.
+     */
+    private final class UserMonitor extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            try {
+                String action = intent.getAction();
+                if (action.equals(Intent.ACTION_USER_REMOVED)) {
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+                    synchronized (mStagedDataLock) {
+                        deleteStagedDataLocked(userId);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in user monitor.", e);
+            }
+        }
+    }
+
+    /**
+     * Helper to monitor package states.
+     *
+     * <p>We're interested in package added, package data cleared and package removed events.
+     */
+    private final class PackageMonitorImpl extends PackageMonitor {
+        @Override
+        public void onPackageAdded(String packageName, int uid) {
+            try {
+                synchronized (mStagedDataLock) {
+                    int userId = UserHandle.getUserId(uid);
+                    if (mStagedData.contains(userId)) {
+                        // Perform lazy restore only if the staged data exists.
+                        doLazyRestoreLocked(packageName, userId);
+                    }
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in onPackageAdded.", e);
+            }
+        }
+
+        @Override
+        public void onPackageDataCleared(String packageName, int uid) {
+            try {
+                notifyBackupManager();
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in onPackageDataCleared.", e);
+            }
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            try {
+                notifyBackupManager();
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception in onPackageRemoved.", e);
+            }
+        }
+    }
+
+    /**
+     * Performs lazy restore from the staged data.
+     *
+     * <p>This is invoked by the package monitor on the package added callback.
+     */
+    private void doLazyRestoreLocked(String packageName, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "doLazyRestore package=" + packageName + " user=" + userId);
+        }
+
+        // Check if the package is installed indeed
+        if (!isPackageInstalledForUser(packageName, userId)) {
+            Slog.e(TAG, packageName + " not installed for user " + userId
+                    + ". Could not restore locales from stage file");
+            return;
+        }
+
+        StagedData stagedData = mStagedData.get(userId);
+        for (String pkgName : stagedData.mPackageStates.keySet()) {
+            String languageTags = stagedData.mPackageStates.get(pkgName);
+
+            if (pkgName.equals(packageName)) {
+
+                checkExistingLocalesAndApplyRestore(pkgName, languageTags, userId);
+
+                // Remove the restored entry from the staged data list.
+                stagedData.mPackageStates.remove(pkgName);
+                // Update the file on the disk.
+                writeStageFileLocked(userId);
+
+                // No need to loop further after restoring locales because the staged data will
+                // contain at most one entry for the newly added package.
+                break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerInternal.java b/services/core/java/com/android/server/locales/LocaleManagerInternal.java
new file mode 100644
index 0000000..2db92a5
--- /dev/null
+++ b/services/core/java/com/android/server/locales/LocaleManagerInternal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locales;
+
+import android.annotation.Nullable;
+
+/**
+ * System-server internal interface to the {@link LocaleManagerService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class LocaleManagerInternal {
+    /**
+     * Returns the app-specific locales to be backed up as a data-blob.
+     */
+    public abstract @Nullable byte[] getBackupPayload(int userId);
+
+    /**
+     * Restores the app-locales that were previously backed up.
+     *
+     * <p>This method will parse the input data blob and restore the locales for apps which are
+     * present on the device. It will stage the locale data for the apps which are not installed
+     * at the time this is called, to be referenced later when the app is installed.
+     */
+    public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
+}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 457c2fd..6aabdb5 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -20,6 +20,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.ILocaleManager;
@@ -29,6 +30,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.os.Binder;
 import android.os.LocaleList;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -51,11 +53,14 @@
  */
 public class LocaleManagerService extends SystemService {
     private static final String TAG = "LocaleManagerService";
-    private final Context mContext;
+    final Context mContext;
     private final LocaleManagerService.LocaleManagerBinderService mBinderService;
     private ActivityTaskManagerInternal mActivityTaskManagerInternal;
     private ActivityManagerInternal mActivityManagerInternal;
     private PackageManagerInternal mPackageManagerInternal;
+
+    private LocaleManagerBackupHelper mBackupHelper;
+
     public static final boolean DEBUG = false;
 
     public LocaleManagerService(Context context) {
@@ -65,23 +70,48 @@
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        mBackupHelper = new LocaleManagerBackupHelper(this,
+                mPackageManagerInternal);
     }
 
     @VisibleForTesting
     LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
             ActivityManagerInternal activityManagerInternal,
-            PackageManagerInternal packageManagerInternal) {
+            PackageManagerInternal packageManagerInternal,
+            LocaleManagerBackupHelper localeManagerBackupHelper) {
         super(context);
         mContext = context;
         mBinderService = new LocaleManagerBinderService();
         mActivityTaskManagerInternal = activityTaskManagerInternal;
         mActivityManagerInternal = activityManagerInternal;
         mPackageManagerInternal = packageManagerInternal;
+        mBackupHelper = localeManagerBackupHelper;
     }
 
     @Override
     public void onStart() {
         publishBinderService(Context.LOCALE_SERVICE, mBinderService);
+        LocalServices.addService(LocaleManagerInternal.class, new LocaleManagerInternalImpl());
+    }
+
+    private final class LocaleManagerInternalImpl extends LocaleManagerInternal {
+
+        @Override
+        public @Nullable byte[] getBackupPayload(int userId) {
+            checkCallerIsSystem();
+            return mBackupHelper.getBackupPayload(userId);
+        }
+
+        @Override
+        public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
+            mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
+        }
+
+        private void checkCallerIsSystem() {
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Caller is not system.");
+            }
+        }
     }
 
     private final class LocaleManagerBinderService extends ILocaleManager.Stub {
@@ -110,6 +140,7 @@
             (new LocaleManagerShellCommand(mBinderService))
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
+
     }
 
     /**
@@ -161,6 +192,8 @@
             notifyAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyInstallerOfAppWhoseLocaleChanged(appPackageName, userId, locales);
             notifyRegisteredReceivers(appPackageName, userId, locales);
+
+            mBackupHelper.notifyBackupManager();
         }
     }
 
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2550e3a..ffef803 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -112,7 +112,6 @@
 import com.android.server.location.injector.DeviceStationaryHelper;
 import com.android.server.location.injector.EmergencyHelper;
 import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.LocationAttributionHelper;
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationUsageLogger;
@@ -1705,7 +1704,6 @@
         private final SystemScreenInteractiveHelper mScreenInteractiveHelper;
         private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
         private final SystemDeviceIdleHelper mDeviceIdleHelper;
-        private final LocationAttributionHelper mLocationAttributionHelper;
         private final LocationUsageLogger mLocationUsageLogger;
 
         // lazily instantiated since they may not always be used
@@ -1731,7 +1729,6 @@
             mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context);
             mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
             mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
-            mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
             mLocationUsageLogger = new LocationUsageLogger();
         }
 
@@ -1808,11 +1805,6 @@
         }
 
         @Override
-        public LocationAttributionHelper getLocationAttributionHelper() {
-            return mLocationAttributionHelper;
-        }
-
-        @Override
         public synchronized EmergencyHelper getEmergencyHelper() {
             if (mEmergencyCallHelper == null) {
                 mEmergencyCallHelper = new SystemEmergencyHelper(mContext);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index efd3037..0fd7cc1 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -427,6 +427,11 @@
         onClientExit();
     }
 
+    @Override
+    public int getId() {
+        return mHostEndPointId;
+    }
+
     /**
      * Invoked when the underlying binder of this broker has died at the client process.
      */
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 389adb0..fc9697d 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -72,6 +72,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -709,7 +710,8 @@
             long now = SystemClock.elapsedRealtimeNanos();
             long lastRestartTimeNs = mLastRestartTimestampMap.get(contextHubId).getAndSet(now);
             ContextHubStatsLog.write(
-                    ContextHubStatsLog.CONTEXT_HUB_RESTARTED, now - lastRestartTimeNs,
+                    ContextHubStatsLog.CONTEXT_HUB_RESTARTED,
+                    TimeUnit.NANOSECONDS.toMillis(now - lastRestartTimeNs),
                     contextHubId);
 
             sendLocationSettingUpdate();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index 2cdf75d..8fdde24 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -29,6 +29,7 @@
 import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.NanoAppBinary;
 import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppRpcService;
 import android.hardware.location.NanoAppState;
 import android.util.Log;
 
@@ -211,9 +212,14 @@
             android.hardware.contexthub.NanoappInfo[] nanoAppInfoList) {
         ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
         for (android.hardware.contexthub.NanoappInfo appInfo : nanoAppInfoList) {
+            ArrayList<NanoAppRpcService> rpcServiceList = new ArrayList<>();
+            for (android.hardware.contexthub.NanoappRpcService service : appInfo.rpcServices) {
+                rpcServiceList.add(new NanoAppRpcService(service.id, service.version));
+            }
             nanoAppStateList.add(
                     new NanoAppState(appInfo.nanoappId, appInfo.nanoappVersion,
-                            appInfo.enabled, new ArrayList<>(Arrays.asList(appInfo.permissions))));
+                            appInfo.enabled, new ArrayList<>(Arrays.asList(appInfo.permissions)),
+                            rpcServiceList));
         }
 
         return nanoAppStateList;
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index 1781588..699f143 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location.gnss;
 
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+
 import static com.android.server.location.gnss.GnssManagerService.D;
 import static com.android.server.location.gnss.GnssManagerService.TAG;
 
@@ -32,7 +34,6 @@
 import com.android.server.location.gnss.hal.GnssNative;
 import com.android.server.location.injector.AppOpsHelper;
 import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.LocationAttributionHelper;
 import com.android.server.location.injector.LocationUsageLogger;
 import com.android.server.location.injector.SettingsHelper;
 
@@ -68,25 +69,23 @@
         @Nullable
         @Override
         protected void onActive() {
-            mLocationAttributionHelper.reportHighPowerLocationStart(getIdentity());
+            mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
         }
 
         @Nullable
         @Override
         protected void onInactive() {
-            mLocationAttributionHelper.reportHighPowerLocationStop(getIdentity());
+            mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
         }
     }
 
     private final AppOpsHelper mAppOpsHelper;
-    private final LocationAttributionHelper mLocationAttributionHelper;
     private final LocationUsageLogger mLogger;
     private final GnssNative mGnssNative;
 
     public GnssMeasurementsProvider(Injector injector, GnssNative gnssNative) {
         super(injector);
         mAppOpsHelper = injector.getAppOpsHelper();
-        mLocationAttributionHelper = injector.getLocationAttributionHelper();
         mLogger = injector.getLocationUsageLogger();
         mGnssNative = gnssNative;
 
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index 173fd13..c0ce3a6 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -58,9 +58,6 @@
     /** Returns a DeviceIdleHelper. */
     DeviceIdleHelper getDeviceIdleHelper();
 
-    /** Returns a LocationAttributionHelper. */
-    LocationAttributionHelper getLocationAttributionHelper();
-
     /** Returns an EmergencyHelper. */
     EmergencyHelper getEmergencyHelper();
 
diff --git a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
deleted file mode 100644
index 4838752..0000000
--- a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java
+++ /dev/null
@@ -1,122 +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.location.injector;
-
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-
-import static com.android.server.location.LocationManagerService.D;
-import static com.android.server.location.LocationManagerService.TAG;
-
-import android.location.util.identity.CallerIdentity;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Map;
-
-/**
- * Helps manage appop monitoring for multiple location clients.
- */
-public class LocationAttributionHelper {
-
-    private final AppOpsHelper mAppOpsHelper;
-
-    @GuardedBy("this")
-    private final Map<CallerIdentity, Integer> mAttributions;
-    @GuardedBy("this")
-    private final Map<CallerIdentity, Integer> mHighPowerAttributions;
-
-    public LocationAttributionHelper(AppOpsHelper appOpsHelper) {
-        mAppOpsHelper = appOpsHelper;
-
-        mAttributions = new ArrayMap<>();
-        mHighPowerAttributions = new ArrayMap<>();
-    }
-
-    /**
-     * Report normal location usage for the given caller in the given bucket, with a unique key.
-     */
-    public synchronized void reportLocationStart(CallerIdentity identity) {
-        identity = CallerIdentity.forAggregation(identity);
-
-        int count = mAttributions.getOrDefault(identity, 0);
-        if (count == 0) {
-            if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) {
-                mAttributions.put(identity, 1);
-            }
-        } else {
-            mAttributions.put(identity, count + 1);
-        }
-    }
-
-    /**
-     * Report normal location usage has stopped for the given caller in the given bucket, with a
-     * unique key.
-     */
-    public synchronized void reportLocationStop(CallerIdentity identity) {
-        identity = CallerIdentity.forAggregation(identity);
-
-        int count = mAttributions.getOrDefault(identity, 0);
-        if (count == 1) {
-            mAttributions.remove(identity);
-            mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity);
-        } else if (count > 1) {
-            mAttributions.put(identity, count - 1);
-        }
-    }
-
-    /**
-     * Report high power location usage for the given caller in the given bucket, with a unique
-     * key.
-     */
-    public synchronized void reportHighPowerLocationStart(CallerIdentity identity) {
-        identity = CallerIdentity.forAggregation(identity);
-
-        int count = mHighPowerAttributions.getOrDefault(identity, 0);
-        if (count == 0) {
-            if (D) {
-                Log.v(TAG, "starting high power location attribution for " + identity);
-            }
-            if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) {
-                mHighPowerAttributions.put(identity, 1);
-            }
-        } else {
-            mHighPowerAttributions.put(identity, count + 1);
-        }
-    }
-
-    /**
-     * Report high power location usage has stopped for the given caller in the given bucket,
-     * with a unique key.
-     */
-    public synchronized void reportHighPowerLocationStop(CallerIdentity identity) {
-        identity = CallerIdentity.forAggregation(identity);
-
-        int count = mHighPowerAttributions.getOrDefault(identity, 0);
-        if (count == 1) {
-            if (D) {
-                Log.v(TAG, "stopping high power location attribution for " + identity);
-            }
-            mHighPowerAttributions.remove(identity);
-            mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity);
-        } else if (count > 1) {
-            mHighPowerAttributions.put(identity, count - 1);
-        }
-    }
-}
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 0ce24dd..e7cc7b6 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.location.provider;
 
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
 import static android.app.compat.CompatChanges.isChangeEnabled;
 import static android.location.LocationManager.DELIVER_HISTORICAL_LOCATIONS;
 import static android.location.LocationManager.GPS_PROVIDER;
@@ -32,6 +34,7 @@
 import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF;
 import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF;
 
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 import static com.android.server.location.LocationManagerService.D;
 import static com.android.server.location.LocationManagerService.TAG;
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -98,7 +101,6 @@
 import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
 import com.android.server.location.injector.AppOpsHelper;
 import com.android.server.location.injector.Injector;
-import com.android.server.location.injector.LocationAttributionHelper;
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
@@ -191,7 +193,7 @@
 
         private final ILocationListener mListener;
 
-        protected LocationListenerTransport(ILocationListener listener) {
+        LocationListenerTransport(ILocationListener listener) {
             mListener = Objects.requireNonNull(listener);
         }
 
@@ -287,7 +289,7 @@
 
         private final ILocationCallback mCallback;
 
-        protected GetCurrentLocationTransport(ILocationCallback callback) {
+        GetCurrentLocationTransport(ILocationCallback callback) {
             mCallback = Objects.requireNonNull(callback);
         }
 
@@ -398,7 +400,7 @@
             EVENT_LOG.logProviderClientActive(mName, getIdentity());
 
             if (!getRequest().isHiddenFromAppOps()) {
-                mLocationAttributionHelper.reportLocationStart(getIdentity());
+                mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, getIdentity());
             }
             onHighPowerUsageChanged();
 
@@ -413,7 +415,7 @@
 
             onHighPowerUsageChanged();
             if (!getRequest().isHiddenFromAppOps()) {
-                mLocationAttributionHelper.reportLocationStop(getIdentity());
+                mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, getIdentity());
             }
 
             onProviderListenerInactive();
@@ -487,11 +489,9 @@
 
                 if (!getRequest().isHiddenFromAppOps()) {
                     if (mIsUsingHighPower) {
-                        mLocationAttributionHelper.reportHighPowerLocationStart(
-                                getIdentity());
+                        mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
                     } else {
-                        mLocationAttributionHelper.reportHighPowerLocationStop(
-                                getIdentity());
+                        mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, getIdentity());
                     }
                 }
             }
@@ -994,7 +994,7 @@
     protected final class LocationListenerRegistration extends LocationRegistration implements
             IBinder.DeathRecipient {
 
-        protected LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
+        LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
                 LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
             super(request, identity, transport, permissionLevel);
         }
@@ -1059,7 +1059,7 @@
     protected final class LocationPendingIntentRegistration extends LocationRegistration implements
             PendingIntent.CancelListener {
 
-        protected LocationPendingIntentRegistration(LocationRequest request,
+        LocationPendingIntentRegistration(LocationRequest request,
                 CallerIdentity identity, LocationPendingIntentTransport transport,
                 @PermissionLevel int permissionLevel) {
             super(request, identity, transport, permissionLevel);
@@ -1068,13 +1068,15 @@
         @GuardedBy("mLock")
         @Override
         protected void onLocationListenerRegister() {
-            ((PendingIntent) getKey()).registerCancelListener(this);
+            if (!((PendingIntent) getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
+                remove();
+            }
         }
 
         @GuardedBy("mLock")
         @Override
         protected void onLocationListenerUnregister() {
-            ((PendingIntent) getKey()).unregisterCancelListener(this);
+            ((PendingIntent) getKey()).removeCancelListener(this);
         }
 
         @Override
@@ -1117,7 +1119,7 @@
 
         private long mExpirationRealtimeMs = Long.MAX_VALUE;
 
-        protected GetCurrentLocationListenerRegistration(LocationRequest request,
+        GetCurrentLocationListenerRegistration(LocationRequest request,
                 CallerIdentity identity, LocationTransport transport, int permissionLevel) {
             super(request, identity, transport, permissionLevel);
         }
@@ -1326,7 +1328,6 @@
     protected final AppForegroundHelper mAppForegroundHelper;
     protected final LocationPowerSaveModeHelper mLocationPowerSaveModeHelper;
     protected final ScreenInteractiveHelper mScreenInteractiveHelper;
-    protected final LocationAttributionHelper mLocationAttributionHelper;
     protected final LocationUsageLogger mLocationUsageLogger;
     protected final LocationFudger mLocationFudger;
 
@@ -1394,7 +1395,6 @@
         mAppForegroundHelper = injector.getAppForegroundHelper();
         mLocationPowerSaveModeHelper = injector.getLocationPowerSaveModeHelper();
         mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
-        mLocationAttributionHelper = injector.getLocationAttributionHelper();
         mLocationUsageLogger = injector.getLocationUsageLogger();
         mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
 
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 22a675a..5e38bca 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -23,6 +23,8 @@
 import static com.android.server.location.LocationManagerService.TAG;
 import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
 
+import static java.lang.Math.max;
+
 import android.annotation.Nullable;
 import android.location.Location;
 import android.location.LocationResult;
@@ -53,6 +55,7 @@
         implements DeviceIdleHelper.DeviceIdleListener, DeviceIdleInternal.StationaryListener {
 
     private static final long MAX_STATIONARY_LOCATION_AGE_MS = 30000;
+    private static final long MIN_INTERVAL_MS = 1000;
 
     final Object mLock = new Object();
 
@@ -179,7 +182,7 @@
                 && mLastLocation != null
                 && mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
                 <= MAX_STATIONARY_LOCATION_AGE_MS) {
-            throttlingIntervalMs = mIncomingRequest.getIntervalMillis();
+            throttlingIntervalMs = max(mIncomingRequest.getIntervalMillis(), MIN_INTERVAL_MS);
         }
 
         ProviderRequest newRequest;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index d0aa40b..b66c466 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -574,7 +574,13 @@
         }
     }
 
-    private static final class Data {
+    /**
+     * Container class for all networkpolicy events data.
+     *
+     * Note: This class needs to be public for RingBuffer class to be able to create
+     * new instances of this.
+     */
+    public static final class Data {
         public int type;
         public long timeStamp;
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f65deeb..137fc85 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -270,7 +270,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.SomeArgs;
@@ -624,12 +623,6 @@
     private NotificationRecordLogger mNotificationRecordLogger;
     private InstanceIdSequence mNotificationInstanceIdSequence;
     private Set<String> mMsgPkgsAllowedAsConvos = new HashSet();
-    protected static final String ACTION_ENABLE_NAS =
-            "android.server.notification.action.ENABLE_NAS";
-    protected static final String ACTION_DISABLE_NAS =
-            "android.server.notification.action.DISABLE_NAS";
-    protected static final String ACTION_LEARNMORE_NAS =
-            "android.server.notification.action.LEARNMORE_NAS";
 
     static class Archive {
         final SparseArray<Boolean> mEnabled;
@@ -764,97 +757,27 @@
         setDefaultAssistantForUser(userId);
     }
 
-    protected void migrateDefaultNASShowNotificationIfNecessary() {
+    protected void migrateDefaultNAS() {
         final List<UserInfo> activeUsers = mUm.getUsers();
         for (UserInfo userInfo : activeUsers) {
             int userId = userInfo.getUserHandle().getIdentifier();
             if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) {
                 continue;
             }
-            if (mAssistants.hasUserSet(userId)) {
-                ComponentName defaultFromConfig = mAssistants.getDefaultFromConfig();
-                List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
-                if (allowedComponents.size() == 0) {
-                    setNASMigrationDone(userId);
-                    mAssistants.clearDefaults();
-                    continue;
-                } else if (allowedComponents.contains(defaultFromConfig)) {
-                    setNASMigrationDone(userId);
-                    mAssistants.resetDefaultFromConfig();
-                    continue;
-                }
-                // TODO(b/192450820): re-enable when "user set" isn't over triggering
-                //User selected different NAS, need onboarding
-                /*enqueueNotificationInternal(getContext().getPackageName(),
-                        getContext().getOpPackageName(), Binder.getCallingUid(),
-                        Binder.getCallingPid(), TAG,
-                        SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE,
-                        createNASUpgradeNotification(userId), userId);*/
+            List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId);
+            if (allowedComponents.size() == 0) { // user set to none
+                Slog.d(TAG, "NAS Migration: user set to none, disable new NAS setting");
+                setNASMigrationDone(userId);
+                mAssistants.clearDefaults();
+            } else {
+                Slog.d(TAG, "Reset NAS setting and migrate to new default");
+                resetAssistantUserSet(userId);
+                // migrate to new default and set migration done
+                mAssistants.resetDefaultAssistantsIfNecessary();
             }
         }
     }
 
-    protected Notification createNASUpgradeNotification(int userId) {
-        final Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                getContext().getResources().getString(R.string.global_action_settings));
-        int title = R.string.nas_upgrade_notification_title;
-        int content = R.string.nas_upgrade_notification_content;
-
-        Intent onboardingIntent = new Intent(Settings.ACTION_NOTIFICATION_ASSISTANT_SETTINGS);
-        onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
-        Intent enableIntent = new Intent(ACTION_ENABLE_NAS);
-        enableIntent.putExtra(Intent.EXTRA_USER_ID, userId);
-        PendingIntent enableNASPendingIntent = PendingIntent.getBroadcast(getContext(),
-                0, enableIntent, PendingIntent.FLAG_IMMUTABLE);
-
-        Intent disableIntent = new Intent(ACTION_DISABLE_NAS);
-        disableIntent.putExtra(Intent.EXTRA_USER_ID, userId);
-        PendingIntent disableNASPendingIntent = PendingIntent.getBroadcast(getContext(),
-                0, disableIntent, PendingIntent.FLAG_IMMUTABLE);
-
-        Intent learnMoreIntent = new Intent(ACTION_LEARNMORE_NAS);
-        learnMoreIntent.putExtra(Intent.EXTRA_USER_ID, userId);
-        PendingIntent learnNASPendingIntent = PendingIntent.getBroadcast(getContext(),
-                0, learnMoreIntent, PendingIntent.FLAG_IMMUTABLE);
-
-        Notification.Action enableNASAction = new Notification.Action.Builder(
-                0,
-                getContext().getResources().getString(
-                        R.string.nas_upgrade_notification_enable_action),
-                enableNASPendingIntent).build();
-
-        Notification.Action disableNASAction = new Notification.Action.Builder(
-                0,
-                getContext().getResources().getString(
-                        R.string.nas_upgrade_notification_disable_action),
-                disableNASPendingIntent).build();
-
-        Notification.Action learnMoreNASAction = new Notification.Action.Builder(
-                0,
-                getContext().getResources().getString(
-                        R.string.nas_upgrade_notification_learn_more_action),
-                learnNASPendingIntent).build();
-
-
-        return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES)
-                .setAutoCancel(false)
-                .setOngoing(true)
-                .setTicker(getContext().getResources().getString(title))
-                .setSmallIcon(R.drawable.ic_settings_24dp)
-                .setContentTitle(getContext().getResources().getString(title))
-                .setContentText(getContext().getResources().getString(content))
-                .setContentIntent(PendingIntent.getActivity(getContext(), 0, onboardingIntent,
-                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
-                .setLocalOnly(true)
-                .setStyle(new Notification.BigTextStyle())
-                .addAction(enableNASAction)
-                .addAction(disableNASAction)
-                .addAction(learnMoreNASAction)
-                .build();
-    }
-
     @VisibleForTesting
     void setNASMigrationDone(int baseUserId) {
         for (int profileId : mUm.getProfileIds(baseUserId, false)) {
@@ -1882,41 +1805,6 @@
         }
     };
 
-    private final BroadcastReceiver mNASIntentReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
-            if (ACTION_ENABLE_NAS.equals(action)) {
-                mAssistants.resetDefaultFromConfig();
-                setNotificationAssistantAccessGrantedForUserInternal(
-                        CollectionUtils.firstOrNull(mAssistants.getDefaultComponents()),
-                        userId, true, true);
-                setNASMigrationDone(userId);
-                cancelNotificationInternal(getContext().getPackageName(),
-                        getContext().getOpPackageName(), Binder.getCallingUid(),
-                        Binder.getCallingPid(), TAG,
-                        SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId);
-            } else if (ACTION_DISABLE_NAS.equals(action)) {
-                //Set default NAS to be null if user selected none during migration
-                mAssistants.clearDefaults();
-                setNotificationAssistantAccessGrantedForUserInternal(
-                        null, userId, true, true);
-                setNASMigrationDone(userId);
-                cancelNotificationInternal(getContext().getPackageName(),
-                        getContext().getOpPackageName(), Binder.getCallingUid(),
-                        Binder.getCallingPid(), TAG,
-                        SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId);
-            } else if (ACTION_LEARNMORE_NAS.equals(action)) {
-                Intent i = new Intent(getContext(), NASLearnMoreActivity.class);
-                i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                getContext().sendBroadcastAsUser(
-                        new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.of(userId));
-                getContext().startActivity(i);
-            }
-        }
-    };
-
     private final class SettingsObserver extends ContentObserver {
         private final Uri NOTIFICATION_BADGING_URI
                 = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING);
@@ -2447,12 +2335,6 @@
 
         IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
         getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter);
-
-        IntentFilter nasFilter = new IntentFilter();
-        nasFilter.addAction(ACTION_ENABLE_NAS);
-        nasFilter.addAction(ACTION_DISABLE_NAS);
-        nasFilter.addAction(ACTION_LEARNMORE_NAS);
-        getContext().registerReceiver(mNASIntentReceiver, nasFilter);
     }
 
     /**
@@ -2464,7 +2346,6 @@
         getContext().unregisterReceiver(mNotificationTimeoutReceiver);
         getContext().unregisterReceiver(mRestoreReceiver);
         getContext().unregisterReceiver(mLocaleChangeReceiver);
-        getContext().unregisterReceiver(mNASIntentReceiver);
 
         if (mDeviceConfigChangedListener != null) {
             DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
@@ -2740,7 +2621,7 @@
             mConditionProviders.onBootPhaseAppsCanStart();
             mHistoryManager.onBootPhaseAppsCanStart();
             registerDeviceConfigChange();
-            migrateDefaultNASShowNotificationIfNecessary();
+            migrateDefaultNAS();
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
         }
@@ -5337,10 +5218,6 @@
         public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) {
             checkCallerIsSystem();
             setNASMigrationDone(userId);
-            cancelNotificationInternal(getContext().getPackageName(),
-                    getContext().getOpPackageName(), Binder.getCallingUid(),
-                    Binder.getCallingPid(), TAG,
-                    SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId);
             if (loadFromConfig) {
                 mAssistants.resetDefaultFromConfig();
             } else {
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index e64ec77..24008d0 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.permission.IPermissionManager;
 import android.util.ArrayMap;
@@ -73,7 +74,12 @@
      */
     public boolean hasPermission(int uid) {
         assertFlag();
-        return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
     }
 
     /**
@@ -161,6 +167,7 @@
     public void setNotificationPermission(String packageName, @UserIdInt int userId, boolean grant,
             boolean userSet) {
         assertFlag();
+        final long callingId = Binder.clearCallingIdentity();
         try {
             if (grant) {
                 mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
@@ -174,38 +181,55 @@
             }
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not reach system server", e);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
         }
     }
 
     public void setNotificationPermission(PackagePermission pkgPerm) {
         assertFlag();
-        setNotificationPermission(
-                pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet);
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            setNotificationPermission(
+                    pkgPerm.packageName, pkgPerm.userId, pkgPerm.granted, pkgPerm.userSet);
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
     }
 
     public boolean isPermissionFixed(String packageName, @UserIdInt int userId) {
         assertFlag();
+        final long callingId = Binder.clearCallingIdentity();
         try {
-            int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                    userId);
-            return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                    || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not reach system server", e);
+            try {
+                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
+                        userId);
+                return (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                        || (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not reach system server", e);
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
         }
-        return false;
     }
 
     boolean isPermissionUserSet(String packageName, @UserIdInt int userId) {
         assertFlag();
+        final long callingId = Binder.clearCallingIdentity();
         try {
-            int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
-                    userId);
-            return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "Could not reach system server", e);
+            try {
+                int flags = mPermManager.getPermissionFlags(packageName, NOTIFICATION_PERMISSION,
+                        userId);
+                return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Could not reach system server", e);
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
         }
-        return false;
     }
 
     private void assertFlag() {
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 49e223c..258ae8c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -65,6 +65,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.Preconditions;
@@ -225,7 +226,8 @@
 
         final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1);
         boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE;
-        boolean migrateToPermission = (xmlVersion < XML_VERSION);
+        boolean migrateToPermission =
+                (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled();
         ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>();
         synchronized (mPackagePreferences) {
             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -379,18 +381,16 @@
                             }
 
                             if (migrateToPermission) {
-                                boolean hasChangedChannel = false;
-                                for (NotificationChannel channel : r.channels.values()) {
-                                    if (channel.getUserLockedFields() != 0) {
-                                        hasChangedChannel = true;
-                                        break;
-                                    }
+                                r.importance = appImportance;
+                                if (r.uid != UNKNOWN_UID) {
+                                    // Don't call into permission system until we have a valid uid
+                                    PackagePermission pkgPerm = new PackagePermission(
+                                            r.pkg, UserHandle.getUserId(r.uid),
+                                            r.importance != IMPORTANCE_NONE,
+                                            hasUserConfiguredSettings(r));
+                                    pkgPerms.add(pkgPerm);
                                 }
-                                PackagePermission pkgPerm = new PackagePermission(
-                                        r.pkg, userId, appImportance != IMPORTANCE_NONE,
-                                        hasChangedChannel  || appImportance == IMPORTANCE_NONE);
-                                pkgPerms.add(pkgPerm);
-                            } else {
+                            } else if (!mPermissionHelper.isMigrationEnabled()) {
                                 r.importance = appImportance;
                             }
                         }
@@ -398,11 +398,29 @@
                 }
             }
         }
-        for (PackagePermission p : pkgPerms) {
-            mPermissionHelper.setNotificationPermission(p);
+        if (migrateToPermission) {
+            for (PackagePermission p : pkgPerms) {
+                try {
+                    mPermissionHelper.setNotificationPermission(p);
+                } catch (Exception e) {
+                    Slog.e(TAG, "could not migrate setting for " + p.packageName, e);
+                }
+            }
         }
     }
 
+    @GuardedBy("mPackagePreferences")
+    private boolean hasUserConfiguredSettings(PackagePreferences p){
+        boolean hasChangedChannel = false;
+        for (NotificationChannel channel : p.channels.values()) {
+            if (channel.getUserLockedFields() != 0) {
+                hasChangedChannel = true;
+                break;
+            }
+        }
+        return hasChangedChannel || p.importance == IMPORTANCE_NONE;
+    }
+
     private boolean isShortcutOk(NotificationChannel channel) {
         boolean isInvalidShortcutChannel =
                 channel.getConversationId() != null &&
@@ -2133,6 +2151,10 @@
                 final PackagePreferences r = mPackagePreferences.valueAt(i);
                 event.writeInt(r.uid);
                 event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
+
+                // collect whether this package's importance info was user-set for later, if needed
+                // before the migration is enabled, this will simply default to false in all cases.
+                boolean importanceIsUserSet = false;
                 if (mPermissionHelper.isMigrationEnabled()) {
                     // Even if this package's data is not present, we need to write something;
                     // so default to IMPORTANCE_NONE, since if PM doesn't know about the package
@@ -2140,8 +2162,12 @@
                     int importance = IMPORTANCE_NONE;
                     Pair<Integer, String> key = new Pair<>(r.uid, r.pkg);
                     if (pkgPermissions != null && pkgsWithPermissionsToHandle.contains(key)) {
-                        importance = pkgPermissions.get(key).first
+                        Pair<Boolean, Boolean> permissionPair = pkgPermissions.get(key);
+                        importance = permissionPair.first
                                 ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE;
+                        // cache the second value for writing later
+                        importanceIsUserSet = permissionPair.second;
+
                         pkgsWithPermissionsToHandle.remove(key);
                     }
                     event.writeInt(importance);
@@ -2150,6 +2176,7 @@
                 }
                 event.writeInt(r.visibility);
                 event.writeInt(r.lockedAppFields);
+                event.writeBoolean(importanceIsUserSet);  // optional bool user_set_importance = 5;
                 events.add(event.build());
             }
         }
@@ -2171,6 +2198,7 @@
                 // builder
                 event.writeInt(DEFAULT_VISIBILITY);
                 event.writeInt(DEFAULT_LOCKED_APP_FIELDS);
+                event.writeBoolean(pkgPermissions.get(p).second); // user_set_importance field
                 events.add(event.build());
             }
         }
@@ -2523,6 +2551,17 @@
                         synchronized (mPackagePreferences) {
                             mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r);
                         }
+                        if (mPermissionHelper.isMigrationEnabled()) {
+                            try {
+                                PackagePermission p = new PackagePermission(
+                                        r.pkg, UserHandle.getUserId(r.uid),
+                                        r.importance != IMPORTANCE_NONE,
+                                        hasUserConfiguredSettings(r));
+                                mPermissionHelper.setNotificationPermission(p);
+                            } catch (Exception e) {
+                                Slog.e(TAG, "could not migrate setting for " + r.pkg, e);
+                            }
+                        }
                         updated = true;
                     } catch (PackageManager.NameNotFoundException e) {
                         // noop
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index fb77d10..0556748 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -34,6 +34,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.parsing.PackageInfoWithoutStateUtils;
 import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ParsedApexSystemService;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Binder;
@@ -53,6 +54,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.build.UnboundedSdkLevel;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
@@ -411,6 +413,11 @@
             throws PackageManagerException;
 
     /**
+     * Get a map of system services defined in an apex mapped to the jar files they reside in.
+     */
+    public abstract Map<String, String> getApexSystemServices();
+
+    /**
      * Dumps various state information to the provided {@link PrintWriter} object.
      *
      * @param pw the {@link PrintWriter} object to send information to.
@@ -438,6 +445,12 @@
         private Set<ActiveApexInfo> mActiveApexInfosCache;
 
         /**
+         * Map of all apex system services to the jar files they are contained in.
+         */
+        @GuardedBy("mLock")
+        private Map<String, String> mApexSystemServices = new ArrayMap<>();
+
+        /**
          * Contains the list of {@code packageName}s of apks-in-apex for given
          * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
          * difference between {@code packageName} and {@code apexModuleName}.
@@ -573,6 +586,32 @@
                                 + ai.modulePath);
                     }
                     mAllPackagesCache.add(packageInfo);
+                    for (ParsedApexSystemService service :
+                            parseResult.parsedPackage.getApexSystemServices()) {
+                        String minSdkVersion = service.getMinSdkVersion();
+                        if (minSdkVersion != null && !UnboundedSdkLevel.isAtLeast(minSdkVersion)) {
+                            Slog.d(TAG, String.format(
+                                    "ApexSystemService %s with min_sdk_version=%s is skipped",
+                                    service.getName(), service.getMinSdkVersion()));
+                            continue;
+                        }
+                        String maxSdkVersion = service.getMaxSdkVersion();
+                        if (maxSdkVersion != null && !UnboundedSdkLevel.isAtMost(maxSdkVersion)) {
+                            Slog.d(TAG, String.format(
+                                    "ApexSystemService %s with max_sdk_version=%s is skipped",
+                                    service.getName(), service.getMaxSdkVersion()));
+                            continue;
+                        }
+
+                        String name = service.getName();
+                        if (mApexSystemServices.containsKey(name)) {
+                            throw new IllegalStateException(String.format(
+                                    "Duplicate apex-system-service %s from %s, %s",
+                                    name, mApexSystemServices.get(name), service.getJarPath()));
+                        }
+
+                        mApexSystemServices.put(name, service.getJarPath());
+                    }
                     mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
                     if (ai.isActive) {
                         if (activePackagesSet.contains(packageInfo.packageName)) {
@@ -1092,6 +1131,15 @@
             }
         }
 
+        @Override
+        public Map<String, String> getApexSystemServices() {
+            synchronized (mLock) {
+                Preconditions.checkState(mApexSystemServices != null,
+                        "APEX packages have not been scanned");
+                return mApexSystemServices;
+            }
+        }
+
         /**
          * Dump information about the packages contained in a particular cache
          * @param packagesCache the cache to print information about.
@@ -1370,6 +1418,13 @@
         }
 
         @Override
+        public Map<String, String> getApexSystemServices() {
+            // TODO(satayev): we can't really support flattened apex use case, and need to migrate
+            // the manifest entries into system's manifest asap.
+            return Collections.emptyMap();
+        }
+
+        @Override
         void dump(PrintWriter pw, String packageName) {
             // No-op
         }
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 6ee1981..bedb8b9 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -74,13 +74,6 @@
         mArtManagerService = mInjector.getArtManagerService();
     }
 
-    AppDataHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
-        mPm = pm;
-        mInjector = injector;
-        mInstaller = injector.getInstaller();
-        mArtManagerService = injector.getArtManagerService();
-    }
-
     /**
      * Prepare app data for the given app just after it was installed or
      * upgraded. This method carefully only touches users that it's installed
@@ -162,7 +155,8 @@
      * </ul>
      */
     private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch,
-            @Nullable AndroidPackage pkg, int previousAppId, int userId, int flags) {
+            @Nullable AndroidPackage pkg, int previousAppId, int userId,
+            @StorageManager.StorageFlags int flags) {
         if (pkg == null) {
             Slog.wtf(TAG, "Package was null!", new Throwable());
             return CompletableFuture.completedFuture(null);
@@ -171,7 +165,8 @@
     }
 
     private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
-            @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
+            @NonNull AndroidPackage pkg, int userId, @StorageManager.StorageFlags int flags,
+            boolean maybeMigrateAppData) {
         prepareAppData(batch, pkg, Process.INVALID_UID, userId, flags).thenRun(() -> {
             // Note: this code block is executed with the Installer lock
             // already held, since it's invoked as a side-effect of
@@ -291,6 +286,9 @@
             String primaryCpuAbi = AndroidPackageUtils.getPrimaryCpuAbi(pkg, pkgSetting);
             if (primaryCpuAbi != null && !VMRuntime.is64BitAbi(primaryCpuAbi)) {
                 final String nativeLibPath = pkg.getNativeLibraryDir();
+                if (!(new File(nativeLibPath).exists())) {
+                    return;
+                }
                 try {
                     mInstaller.linkNativeLibraryDirectory(volumeUuid, packageName,
                             nativeLibPath, userId);
@@ -331,7 +329,8 @@
      * correct for all installed apps on all mounted volumes.
      */
     @NonNull
-    public void reconcileAppsData(int userId, int flags, boolean migrateAppsData) {
+    public void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
+            boolean migrateAppsData) {
         final StorageManager storage = mInjector.getSystemService(StorageManager.class);
         for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
             final String volumeUuid = vol.getFsUuid();
@@ -342,7 +341,7 @@
     }
 
     @GuardedBy("mPm.mInstallLock")
-    void reconcileAppsDataLI(String volumeUuid, int userId, int flags,
+    void reconcileAppsDataLI(String volumeUuid, int userId, @StorageManager.StorageFlags int flags,
             boolean migrateAppData) {
         reconcileAppsDataLI(volumeUuid, userId, flags, migrateAppData, false /* onlyCoreApps */);
     }
@@ -359,8 +358,8 @@
      * @return list of skipped non-core packages (if {@code onlyCoreApps} is true)
      */
     @GuardedBy("mPm.mInstallLock")
-    private List<String> reconcileAppsDataLI(String volumeUuid, int userId, int flags,
-            boolean migrateAppData, boolean onlyCoreApps) {
+    private List<String> reconcileAppsDataLI(String volumeUuid, int userId,
+            @StorageManager.StorageFlags int flags, boolean migrateAppData, boolean onlyCoreApps) {
         Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
                 + Integer.toHexString(flags) + " migrateAppData=" + migrateAppData);
         List<String> result = onlyCoreApps ? new ArrayList<>() : null;
@@ -479,7 +478,7 @@
      * can't wait for user to start
      */
     public Future<?> fixAppsDataOnBoot() {
-        final int storageFlags;
+        final @StorageManager.StorageFlags int storageFlags;
         if (StorageManager.isFileEncryptedNativeOrEmulated()) {
             storageFlags = StorageManager.FLAG_STORAGE_DE;
         } else {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 3e849f8..dd66130 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -133,7 +133,7 @@
     }
     @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
     @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags,
+            @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
             int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits);
     @Computer.LiveImplementation(override = Computer.LiveImplementation.NOT_ALLOWED)
@@ -338,8 +338,8 @@
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @NonNull
-    int[] getPackageGids(@NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
-            @UserIdInt int userId);
+    int[] getPackageGids(@NonNull String packageName,
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     int getTargetSdkVersion(@NonNull String packageName);
@@ -351,12 +351,12 @@
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     ActivityInfo getReceiverInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId);
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId);
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     boolean canRequestPackageInstalls(@NonNull String packageName, int callingUid,
@@ -369,18 +369,18 @@
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId);
+            @PackageManager.PackageInfoFlagsBits long flags, int callingUid, @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
-            @NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     ProviderInfo getProviderInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId);
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
@@ -439,17 +439,18 @@
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @NonNull
     ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(@NonNull String[] permissions,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId);
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @NonNull
-    List<ApplicationInfo> getInstalledApplications(@PackageManager.ApplicationInfoFlags long flags,
-            @UserIdInt int userId, int callingUid);
+    List<ApplicationInfo> getInstalledApplications(
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
+            int callingUid);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
     ProviderInfo resolveContentProvider(@NonNull String name,
-            @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid);
+            @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId, int callingUid);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
@@ -463,7 +464,7 @@
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @NonNull
     ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName, int uid,
-            @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey);
+            @PackageManager.ComponentInfoFlagsBits long flags, @Nullable String metaDataKey);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
     @Nullable
@@ -560,7 +561,7 @@
     boolean canQueryPackage(int callingUid, @Nullable String targetPackageName);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
-    int getPackageUid(@NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
+    int getPackageUid(@NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId);
 
     @Computer.LiveImplementation(override = LiveImplementation.MANDATORY)
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9b21790..1945ed0 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -429,7 +429,7 @@
     }
 
     public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
             int filterCallingUid, int userId, boolean resolveForStart,
             boolean allowDynamicSplits) {
@@ -541,14 +541,14 @@
     }
 
     public final @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return queryIntentActivitiesInternal(
                 intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(),
                 userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
     }
 
     public final @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
             int callingUid, boolean includeInstantApps) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
         enforceCrossUserOrProfilePermission(callingUid,
@@ -625,7 +625,7 @@
     }
 
     protected @NonNull List<ResolveInfo> queryIntentServicesInternalBody(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
             int callingUid, String instantAppPkgName) {
         // reader
         String pkgName = intent.getPackage();
@@ -653,7 +653,7 @@
     }
 
     public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits,
             String pkgName, String instantAppPkgName) {
         // reader
@@ -786,7 +786,7 @@
     }
 
     public final ActivityInfo getActivityInfo(ComponentName component,
-            @PackageManager.ResolveInfoFlags long flags, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
     }
 
@@ -797,7 +797,7 @@
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     public final ActivityInfo getActivityInfoInternal(ComponentName component,
-            @PackageManager.ResolveInfoFlags long flags, int filterCallingUid, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
         if (!mUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId);
 
@@ -811,7 +811,7 @@
     }
 
     protected ActivityInfo getActivityInfoInternalBody(ComponentName component,
-            @PackageManager.ResolveInfoFlags long flags, int filterCallingUid, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) {
         ParsedActivity a = mComponentResolver.getActivity(component);
 
         if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
@@ -879,7 +879,7 @@
     }
 
     public final ApplicationInfo getApplicationInfo(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags, int userId) {
+            @PackageManager.ApplicationInfoFlagsBits long flags, int userId) {
         return getApplicationInfoInternal(packageName, flags, Binder.getCallingUid(), userId);
     }
 
@@ -890,7 +890,7 @@
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     public final ApplicationInfo getApplicationInfoInternal(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags,
+            @PackageManager.ApplicationInfoFlagsBits long flags,
             int filterCallingUid, int userId) {
         if (!mUserManager.exists(userId)) return null;
         flags = updateFlagsForApplication(flags, userId);
@@ -905,7 +905,7 @@
     }
 
     protected ApplicationInfo getApplicationInfoInternalBody(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags,
+            @PackageManager.ApplicationInfoFlagsBits long flags,
             int filterCallingUid, int userId) {
         // writer
         // Normalize package name to handle renamed packages and static libs
@@ -1161,7 +1161,7 @@
     }
 
     public final CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int sourceUserId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId,
             int parentUserId) {
         if (!mUserManager.hasUserRestriction(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING,
                 sourceUserId)) {
@@ -1426,7 +1426,7 @@
     }
 
     private List<ResolveInfo> maybeAddInstantAppInstaller(List<ResolveInfo> result,
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             int userId, boolean resolveForStart, boolean isRequesterInstantApp) {
         // first, check to see if we've got an instant app already installed
         final boolean alreadyResolvedLocally = (flags & PackageManager.MATCH_INSTANT) != 0;
@@ -1532,7 +1532,7 @@
     }
 
     public final PackageInfo generatePackageInfo(PackageStateInternal ps,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         if (!mUserManager.exists(userId)) return null;
         if (ps == null) {
             return null;
@@ -1607,7 +1607,7 @@
     }
 
     public final PackageInfo getPackageInfo(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
                 flags, Binder.getCallingUid(), userId);
     }
@@ -1808,7 +1808,7 @@
     @Nullable
     private CrossProfileDomainInfo createForwardingResolveInfo(
             @NonNull CrossProfileIntentFilter filter, @NonNull Intent intent,
-            @Nullable String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            @Nullable String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             int sourceUserId) {
         int targetUserId = filter.getTargetUserId();
         if (!isUserEnabled(targetUserId)) {
@@ -1970,7 +1970,7 @@
     }
 
     public final ServiceInfo getServiceInfo(ComponentName component,
-            @PackageManager.ResolveInfoFlags long flags, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
@@ -1981,7 +1981,7 @@
     }
 
     protected ServiceInfo getServiceInfoBody(ComponentName component,
-            @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid) {
         ParsedService s = mComponentResolver.getService(component);
         if (DEBUG_PACKAGE_INFO) {
             Log.v(
@@ -2233,7 +2233,7 @@
     }
 
     private boolean filterStaticSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
-            int userId, @PackageManager.ComponentInfoFlags long flags) {
+            int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
         // Callers can access only the static shared libs they depend on, otherwise they need to
         // explicitly ask for the static shared libraries given the caller is allowed to access
         // all static libs.
@@ -2289,7 +2289,7 @@
     }
 
     private boolean filterSdkLibPackage(@Nullable PackageStateInternal ps, int uid,
-            int userId, @PackageManager.ComponentInfoFlags long flags) {
+            int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
         // Callers can access only the SDK libs they depend on, otherwise they need to
         // explicitly ask for the SDKs given the caller is allowed to access
         // all shared libs.
@@ -2346,7 +2346,7 @@
 
     @Override
     public final boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
-            int userId, @PackageManager.ComponentInfoFlags long flags) {
+            int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
         return filterStaticSharedLibPackage(ps, uid, userId, flags) || filterSdkLibPackage(ps, uid,
                 userId, flags);
     }
@@ -2445,7 +2445,7 @@
      * activity was not set by the DPC.
      */
     public final boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent,
-            int userId, String resolvedType, @PackageManager.ResolveInfoFlags long flags) {
+            int userId, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags) {
         return intent.isImplicitImageCaptureIntent() && !isPersistentPreferredActivitySetByDpm(
                 intent, userId, resolvedType, flags);
     }
@@ -2486,7 +2486,7 @@
 
     private boolean isInstantAppResolutionAllowed(
             Intent intent, List<ResolveInfo> resolvedActivities, int userId,
-            boolean skipPackageCheck, @PackageManager.ResolveInfoFlags long flags) {
+            boolean skipPackageCheck, @PackageManager.ResolveInfoFlagsBits long flags) {
         if (mInstantAppResolverConnection == null) {
             return false;
         }
@@ -2526,7 +2526,7 @@
     // Or if there's already an ephemeral app installed that handles the action
     protected boolean isInstantAppResolutionAllowedBody(
             Intent intent, List<ResolveInfo> resolvedActivities, int userId,
-            boolean skipPackageCheck, @PackageManager.ResolveInfoFlags long flags) {
+            boolean skipPackageCheck, @PackageManager.ResolveInfoFlagsBits long flags) {
         final int count = (resolvedActivities == null ? 0 : resolvedActivities.size());
         for (int n = 0; n < count; n++) {
             final ResolveInfo info = resolvedActivities.get(n);
@@ -2558,7 +2558,7 @@
     }
 
     private boolean isPersistentPreferredActivitySetByDpm(Intent intent, int userId,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags) {
         PersistentPreferredIntentResolver ppir =
                 mSettings.getPersistentPreferredActivities(userId);
         //TODO(b/158003772): Remove double query
@@ -2716,7 +2716,7 @@
     }
 
     public int getPackageUidInternal(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId, int callingUid) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
         // reader
         final AndroidPackage p = mPackages.get(packageName);
         if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) {
@@ -3156,7 +3156,7 @@
                 try {
                     mDomainVerificationManager.printState(writer, packageName,
                             UserHandle.USER_ALL, mSettings::getPackage);
-                } catch (PackageManager.NameNotFoundException e) {
+                } catch (Exception e) {
                     pw.println("Failure printing domain verification information");
                     Slog.e(TAG, "Failure printing domain verification information", e);
                 }
@@ -3239,7 +3239,7 @@
 
     // The body of findPreferredActivity.
     protected PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityBody(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean always,
             boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
             int callingUid, boolean isDeviceProvisioned) {
@@ -3448,7 +3448,7 @@
     }
 
     public final PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityInternal(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean always,
             boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
 
@@ -3465,7 +3465,7 @@
     }
 
     public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean debug, int userId) {
         final int n = query.size();
         PersistentPreferredIntentResolver ppir =
@@ -3663,7 +3663,7 @@
 
     @Override
     public int[] getPackageGids(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId);
@@ -3740,7 +3740,7 @@
     @Nullable
     @Override
     public ActivityInfo getReceiverInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
@@ -3773,7 +3773,7 @@
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return null;
         Preconditions.checkArgumentNonnegative(userId, "userId must be >= 0");
         final int callingUid = Binder.getCallingUid();
@@ -3902,7 +3902,8 @@
 
     @Override
     public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int callingUid,
+            @UserIdInt int userId) {
         List<VersionedPackage> versionedPackages = null;
         final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates();
         final int packageCount = packageStates.size();
@@ -3964,7 +3965,7 @@
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
-            @NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES,
                 "getDeclaredSharedLibraries");
@@ -4041,7 +4042,7 @@
     @Nullable
     @Override
     public ProviderInfo getProviderInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId);
@@ -4515,7 +4516,7 @@
     @NonNull
     @Override
     public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(
-            @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String[] permissions, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId);
@@ -4536,7 +4537,7 @@
     }
 
     private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageStateInternal ps,
-            String[] permissions, boolean[] tmp, @PackageManager.PackageInfoFlags long flags,
+            String[] permissions, boolean[] tmp, @PackageManager.PackageInfoFlagsBits long flags,
             int userId) {
         int numMatch = 0;
         for (int i=0; i<permissions.length; i++) {
@@ -4578,7 +4579,7 @@
     @NonNull
     @Override
     public List<ApplicationInfo> getInstalledApplications(
-            @PackageManager.ApplicationInfoFlags long flags, @UserIdInt int userId,
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
             int callingUid) {
         if (getInstantAppPackageName(callingUid) != null) {
             return Collections.emptyList();
@@ -4629,9 +4630,9 @@
             }
         } else {
             list = new ArrayList<>(mPackages.size());
-            for (PackageStateInternal packageState : packageStates.values()) {
-                final AndroidPackage pkg = packageState.getPkg();
-                if (pkg == null) {
+            for (AndroidPackage p : mPackages.values()) {
+                final PackageStateInternal packageState = packageStates.get(p.getPackageName());
+                if (packageState == null) {
                     continue;
                 }
                 if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) {
@@ -4640,10 +4641,10 @@
                 if (shouldFilterApplication(packageState, callingUid, userId)) {
                     continue;
                 }
-                ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(pkg, flags,
+                ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(p, flags,
                         packageState.getUserStateOrDefault(userId), userId, packageState);
                 if (ai != null) {
-                    ai.packageName = resolveExternalPackageName(pkg);
+                    ai.packageName = resolveExternalPackageName(p);
                     list.add(ai);
                 }
             }
@@ -4655,7 +4656,8 @@
     @Nullable
     @Override
     public ProviderInfo resolveContentProvider(@NonNull String name,
-            @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid) {
+            @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+            int callingUid) {
         if (!mUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId);
         final ProviderInfo providerInfo = mComponentResolver.queryProvider(name, flags, userId);
@@ -4744,7 +4746,8 @@
     @NonNull
     @Override
     public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName,
-            int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) {
+            int uid, @PackageManager.ComponentInfoFlagsBits long flags,
+            @Nullable String metaDataKey) {
         final int callingUid = Binder.getCallingUid();
         final int userId = processName != null ? UserHandle.getUserId(uid)
                 : UserHandle.getCallingUserId();
@@ -5290,7 +5293,7 @@
 
     @Override
     public int getPackageUid(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         if (!mUserManager.exists(userId)) return -1;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId);
diff --git a/services/core/java/com/android/server/pm/ComputerLocked.java b/services/core/java/com/android/server/pm/ComputerLocked.java
index f180d19..bd730e9 100644
--- a/services/core/java/com/android/server/pm/ComputerLocked.java
+++ b/services/core/java/com/android/server/pm/ComputerLocked.java
@@ -314,7 +314,7 @@
 
     @Override
     public int[] getPackageGids(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getPackageGids(packageName, flags, userId);
         }
@@ -339,7 +339,7 @@
     @Nullable
     @Override
     public ActivityInfo getReceiverInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getReceiverInfo(component, flags, userId);
         }
@@ -348,7 +348,7 @@
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getSharedLibraries(packageName, flags, userId);
         }
@@ -365,7 +365,8 @@
 
     @Override
     public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int callingUid,
+            @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId);
         }
@@ -374,7 +375,7 @@
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
-            @NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getDeclaredSharedLibraries(packageName, flags, userId);
@@ -384,7 +385,7 @@
     @Nullable
     @Override
     public ProviderInfo getProviderInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getProviderInfo(component, flags, userId);
         }
@@ -498,7 +499,7 @@
     @NonNull
     @Override
     public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(
-            @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String[] permissions, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getPackagesHoldingPermissions(permissions, flags, userId);
@@ -508,7 +509,7 @@
     @NonNull
     @Override
     public List<ApplicationInfo> getInstalledApplications(
-            @PackageManager.ApplicationInfoFlags long flags, @UserIdInt int userId,
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
             int callingUid) {
         synchronized (mLock) {
             return super.getInstalledApplications(flags, userId, callingUid);
@@ -518,7 +519,8 @@
     @Nullable
     @Override
     public ProviderInfo resolveContentProvider(@NonNull String name,
-            @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid) {
+            @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+            int callingUid) {
         synchronized (mLock) {
             return super.resolveContentProvider(name, flags, userId, callingUid);
         }
@@ -544,7 +546,8 @@
     @NonNull
     @Override
     public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName,
-            int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) {
+            int uid, @PackageManager.ComponentInfoFlagsBits long flags,
+            @Nullable String metaDataKey) {
         synchronized (mLock) {
             return super.queryContentProviders(processName, uid, flags, metaDataKey);
         }
@@ -717,7 +720,7 @@
 
     @Override
     public int getPackageUid(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         synchronized (mLock) {
             return super.getPackageUid(packageName, flags, userId);
         }
diff --git a/services/core/java/com/android/server/pm/ComputerTracker.java b/services/core/java/com/android/server/pm/ComputerTracker.java
index a3cd092..e6ff836 100644
--- a/services/core/java/com/android/server/pm/ComputerTracker.java
+++ b/services/core/java/com/android/server/pm/ComputerTracker.java
@@ -97,7 +97,7 @@
     }
 
     public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
             int filterCallingUid, int userId, boolean resolveForStart,
             boolean allowDynamicSplits) {
@@ -111,7 +111,7 @@
         }
     }
     public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         ThreadComputer current = snapshot();
         try {
             return current.mComputer.queryIntentActivitiesInternal(intent, resolvedType, flags,
@@ -121,7 +121,7 @@
         }
     }
     public @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
             int callingUid, boolean includeInstantApps) {
         ThreadComputer current = snapshot();
         try {
@@ -132,7 +132,7 @@
         }
     }
     public @NonNull QueryIntentActivitiesResult queryIntentActivitiesInternalBody(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits,
             String pkgName, String instantAppPkgName) {
         ThreadComputer current = live();
@@ -145,7 +145,7 @@
         }
     }
     public ActivityInfo getActivityInfo(ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
         ThreadComputer current = snapshot();
         try {
             return current.mComputer.getActivityInfo(component, flags, userId);
@@ -154,7 +154,7 @@
         }
     }
     public ActivityInfo getActivityInfoInternal(ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags,
+            @PackageManager.ComponentInfoFlagsBits long flags,
             int filterCallingUid, int userId) {
         ThreadComputer current = live();
         try {
@@ -191,7 +191,7 @@
         }
     }
     public ApplicationInfo getApplicationInfo(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags, int userId) {
+            @PackageManager.ApplicationInfoFlagsBits long flags, int userId) {
         ThreadComputer current = snapshot();
         try {
             return current.mComputer.getApplicationInfo(packageName, flags, userId);
@@ -200,7 +200,7 @@
         }
     }
     public ApplicationInfo getApplicationInfoInternal(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) {
+            @PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId) {
         ThreadComputer current = live();
         try {
             return current.mComputer.getApplicationInfoInternal(packageName, flags,
@@ -227,7 +227,7 @@
         }
     }
     public CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int sourceUserId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId,
             int parentUserId) {
         ThreadComputer current = live();
         try {
@@ -268,7 +268,7 @@
         }
     }
     public PackageInfo generatePackageInfo(PackageStateInternal ps,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         ThreadComputer current = live();
         try {
             return current.mComputer.generatePackageInfo(ps, flags, userId);
@@ -277,7 +277,7 @@
         }
     }
     public PackageInfo getPackageInfo(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         ThreadComputer current = snapshot();
         try {
             return current.mComputer.getPackageInfo(packageName, flags, userId);
@@ -341,7 +341,7 @@
         }
     }
     public ServiceInfo getServiceInfo(ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
         ThreadComputer current = live();
         try {
             return current.mComputer.getServiceInfo(component, flags, userId);
@@ -446,7 +446,7 @@
         }
     }
     public boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
-            int userId, @PackageManager.ComponentInfoFlags long flags) {
+            int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
         ThreadComputer current = live();
         try {
             return current.mComputer.filterSharedLibPackage(ps, uid, userId, flags);
@@ -480,7 +480,7 @@
         }
     }
     public boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent,
-            int userId, String resolvedType, @PackageManager.ResolveInfoFlags long flags) {
+            int userId, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags) {
         ThreadComputer current = live();
         try {
             return current.mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent,
@@ -553,7 +553,7 @@
         }
     }
     public int getPackageUidInternal(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId, int callingUid) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
         ThreadComputer current = live();
         try {
             return current.mComputer.getPackageUidInternal(packageName, flags, userId,
@@ -648,7 +648,7 @@
         }
     }
     public PackageManagerService.FindPreferredActivityBodyResult findPreferredActivityInternal(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug,
             int userId, boolean queryMayBeFiltered) {
         ThreadComputer current = live();
@@ -660,7 +660,7 @@
         }
     }
     public ResolveInfo findPersistentPreferredActivityLP(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean debug, int userId) {
         ThreadComputer current = live();
         try {
@@ -749,7 +749,7 @@
 
     @Override
     public int[] getPackageGids(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getPackageGids(packageName, flags, userId);
         }
@@ -774,7 +774,7 @@
     @Nullable
     @Override
     public ActivityInfo getReceiverInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getReceiverInfo(component, flags, userId);
         }
@@ -783,7 +783,7 @@
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getSharedLibraries(packageName, flags, userId);
         }
@@ -808,7 +808,8 @@
 
     @Override
     public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo,
-            @PackageManager.PackageInfoFlags long flags, int callingUid, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int callingUid,
+            @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getPackagesUsingSharedLibrary(libInfo, flags, callingUid,
                     userId);
@@ -818,7 +819,7 @@
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
-            @NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getDeclaredSharedLibraries(packageName, flags, userId);
@@ -828,7 +829,7 @@
     @Nullable
     @Override
     public ProviderInfo getProviderInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getProviderInfo(component, flags, userId);
         }
@@ -943,7 +944,7 @@
     @NonNull
     @Override
     public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(
-            @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String[] permissions, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getPackagesHoldingPermissions(permissions, flags, userId);
@@ -953,7 +954,7 @@
     @NonNull
     @Override
     public List<ApplicationInfo> getInstalledApplications(
-            @PackageManager.ApplicationInfoFlags long flags, @UserIdInt int userId,
+            @PackageManager.ApplicationInfoFlagsBits long flags, @UserIdInt int userId,
             int callingUid) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getInstalledApplications(flags, userId, callingUid);
@@ -963,7 +964,8 @@
     @Nullable
     @Override
     public ProviderInfo resolveContentProvider(@NonNull String name,
-            @PackageManager.ResolveInfoFlags long flags, @UserIdInt int userId, int callingUid) {
+            @PackageManager.ResolveInfoFlagsBits long flags, @UserIdInt int userId,
+            int callingUid) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.resolveContentProvider(name, flags, userId, callingUid);
         }
@@ -990,7 +992,8 @@
     @NonNull
     @Override
     public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable String processName,
-            int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) {
+            int uid, @PackageManager.ComponentInfoFlagsBits long flags,
+            @Nullable String metaDataKey) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.queryContentProviders(processName, uid, flags, metaDataKey);
         }
@@ -1164,7 +1167,7 @@
 
     @Override
     public int getPackageUid(@NonNull String packageName,
-            @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         try (ThreadComputer current = snapshot()) {
             return current.mComputer.getPackageUid(packageName, flags, userId);
         }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 641f24f..e84d990 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -236,7 +236,9 @@
         if (res) {
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
-            info.sendSystemPackageUpdatedBroadcasts();
+            if (disabledSystemPs != null) {
+                info.sendSystemPackageUpdatedBroadcasts(disabledSystemPs.getAppId());
+            }
         }
         // Force a gc here.
         Runtime.getRuntime().gc();
@@ -251,20 +253,29 @@
 
             if (priorUserStates != null) {
                 synchronized (mPm.mLock) {
-                    for (int i = 0; i < allUsers.length; i++) {
-                        TempUserState priorUserState = priorUserStates.get(allUsers[i]);
-                        int enabledState = priorUserState.enabledState;
-                        PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
-                        pkgSetting.setEnabled(enabledState, allUsers[i],
-                                priorUserState.lastDisableAppCaller);
-
+                    PackageSetting pkgSetting = mPm.getPackageSettingForMutation(packageName);
+                    if (pkgSetting != null) {
                         AndroidPackage aPkg = pkgSetting.getPkg();
                         boolean pkgEnabled = aPkg != null && aPkg.isEnabled();
-                        if (!reEnableStub && priorUserState.installed
-                                && ((enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
-                                || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
-                            reEnableStub = true;
+                        for (int i = 0; i < allUsers.length; i++) {
+                            TempUserState priorUserState = priorUserStates.get(allUsers[i]);
+                            int enabledState = priorUserState.enabledState;
+                            pkgSetting.setEnabled(enabledState, allUsers[i],
+                                    priorUserState.lastDisableAppCaller);
+                            if (!reEnableStub && priorUserState.installed
+                                    && (
+                                    (enabledState == COMPONENT_ENABLED_STATE_DEFAULT && pkgEnabled)
+                                            || enabledState == COMPONENT_ENABLED_STATE_ENABLED)) {
+                                reEnableStub = true;
+                            }
                         }
+                    } else {
+                        // This should not happen. If priorUserStates != null, we are uninstalling
+                        // an update of a system app. In that case, mPm.mSettings.getPackageLpr()
+                        // should return a non-null value for the target packageName because
+                        // restoreDisabledSystemPackageLIF() is called during deletePackageLIF().
+                        Slog.w(TAG, "Missing PackageSetting after uninstalling the update for"
+                                + " system app: " + packageName + ". This should not happen.");
                     }
                     mPm.mSettings.writeAllUsersPackageRestrictionsLPr();
                 }
@@ -422,10 +433,9 @@
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package: " + ps.getPackageName());
             // When an updated system application is deleted we delete the existing resources
             // as well and fall back to existing code in system partition
-            PackageSetting disabledPs = deleteInstalledSystemPackage(action, ps, allUserHandles,
-                    flags, outInfo, writeSettings);
+            deleteInstalledSystemPackage(action, allUserHandles, writeSettings);
             new InstallPackageHelper(mPm).restoreDisabledSystemPackageLIF(
-                    action, ps, allUserHandles, outInfo, writeSettings, disabledPs);
+                    action, allUserHandles, writeSettings);
         } else {
             if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package: " + ps.getPackageName());
             deleteInstalledPackageLIF(ps, deleteCodeAndResources, flags, allUserHandles,
@@ -552,10 +562,11 @@
         mPm.mSettings.writeKernelMappingLPr(ps);
     }
 
-    private PackageSetting deleteInstalledSystemPackage(DeletePackageAction action,
-            PackageSetting deletedPs,
-            @NonNull int[] allUserHandles, int flags, @Nullable PackageRemovedInfo outInfo,
-            boolean writeSettings) {
+    private void deleteInstalledSystemPackage(DeletePackageAction action,
+            @NonNull int[] allUserHandles, boolean writeSettings) {
+        int flags = action.mFlags;
+        final PackageSetting deletedPs = action.mDeletingPs;
+        final PackageRemovedInfo outInfo = action.mRemovedInfo;
         final boolean applyUserRestrictions = outInfo != null && (outInfo.mOrigUsers != null);
         final AndroidPackage deletedPkg = deletedPs.getPkg();
         // Confirm if the system package has been updated
@@ -582,20 +593,19 @@
         if (outInfo != null) {
             // Delete the updated package
             outInfo.mIsRemovedPackageSystemUpdate = true;
+            outInfo.mAppIdChanging = disabledPs.getAppId() != deletedPs.getAppId();
         }
 
-        if (disabledPs.getVersionCode() < deletedPs.getVersionCode()) {
-            // Delete data for downgrades
+        if (disabledPs.getVersionCode() < deletedPs.getVersionCode()
+                || disabledPs.getAppId() != deletedPs.getAppId()) {
+            // Delete data for downgrades, or when the system app changed appId
             flags &= ~PackageManager.DELETE_KEEP_DATA;
         } else {
             // Preserve data by setting flag
             flags |= PackageManager.DELETE_KEEP_DATA;
         }
 
-        deleteInstalledPackageLIF(deletedPs, true, flags, allUserHandles,
-                outInfo, writeSettings);
-
-        return disabledPs;
+        deleteInstalledPackageLIF(deletedPs, true, flags, allUserHandles, outInfo, writeSettings);
     }
 
     public void deletePackageVersionedInternal(VersionedPackage versionedPackage,
diff --git a/services/core/java/com/android/server/pm/DomainVerificationConnection.java b/services/core/java/com/android/server/pm/DomainVerificationConnection.java
index 4ddf2ff..d24435e 100644
--- a/services/core/java/com/android/server/pm/DomainVerificationConnection.java
+++ b/services/core/java/com/android/server/pm/DomainVerificationConnection.java
@@ -76,7 +76,7 @@
 
     @Override
     public long getPowerSaveTempWhitelistAppDuration() {
-        return VerificationUtils.getVerificationTimeout(mPm.mContext);
+        return VerificationUtils.getDefaultVerificationTimeout(mPm.mContext);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
index 509702f..dfa6c66 100644
--- a/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InitAndSystemPackageHelper.java
@@ -126,13 +126,13 @@
         return null;
     }
 
-    public OverlayConfig setUpSystemPackages(
+    public OverlayConfig initPackages(
             WatchedArrayMap<String, PackageSetting> packageSettings, int[] userIds,
             long startTime) {
         PackageParser2 packageParser = mPm.mInjector.getScanningCachingPackageParser();
 
         ExecutorService executorService = ParallelPackageParser.makeExecutorService();
-        // Prepare apex package info before scanning APKs, these information are needed when
+        // Prepare apex package info before scanning APKs, this information is needed when
         // scanning apk in apex.
         mPm.mApexManager.scanApexPackagesTraced(packageParser, executorService);
 
@@ -289,7 +289,7 @@
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            mInstallPackageHelper.installSystemPackagesFromDir(scanDir, parseFlags, scanFlags,
+            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags, scanFlags,
                     currentTime, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d2087ee..8e6746d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -32,8 +32,6 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
 import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_SETUP;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -47,7 +45,6 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
 import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
@@ -56,6 +53,7 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
 import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.DEBUG_UPGRADE;
 import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
 import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -74,13 +72,16 @@
 import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
 import static com.android.server.pm.PackageManagerService.SCAN_IGNORE_FROZEN;
 import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
 import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
 import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
 import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
+import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
 import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
@@ -127,7 +128,6 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -147,14 +147,15 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
-import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
-import com.android.server.Watchdog;
+import com.android.server.pm.dex.ArtManagerService;
+import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.pm.dex.ViewCompiler;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -190,278 +191,47 @@
 import java.util.concurrent.ExecutorService;
 
 final class InstallPackageHelper {
-    /**
-     * Whether verification is enabled by default.
-     */
-    private static final boolean DEFAULT_VERIFY_ENABLE = true;
-
     private final PackageManagerService mPm;
     private final AppDataHelper mAppDataHelper;
-    private final PackageManagerServiceInjector mInjector;
     private final BroadcastHelper mBroadcastHelper;
     private final RemovePackageHelper mRemovePackageHelper;
-    private final ScanPackageHelper mScanPackageHelper;
+    private final StorageManager mStorageManager;
+    private final RollbackManagerInternal mRollbackManager;
+    private final IncrementalManager mIncrementalManager;
+    private final ApexManager mApexManager;
+    private final DexManager mDexManager;
+    private final ArtManagerService mArtManagerService;
+    private final AppOpsManager mAppOpsManager;
+    private final Context mContext;
+    private final PackageDexOptimizer mPackageDexOptimizer;
+    private final PackageAbiHelper mPackageAbiHelper;
+    private final ViewCompiler mViewCompiler;
+    private final IBackupManager mIBackupManager;
 
     // TODO(b/198166813): remove PMS dependency
     InstallPackageHelper(PackageManagerService pm, AppDataHelper appDataHelper) {
         mPm = pm;
-        mInjector = pm.mInjector;
         mAppDataHelper = appDataHelper;
-        mBroadcastHelper = new BroadcastHelper(mInjector);
+        mBroadcastHelper = new BroadcastHelper(pm.mInjector);
         mRemovePackageHelper = new RemovePackageHelper(pm);
-        mScanPackageHelper = new ScanPackageHelper(pm);
+        mStorageManager = pm.mInjector.getSystemService(StorageManager.class);
+        mRollbackManager = pm.mInjector.getLocalService(RollbackManagerInternal.class);
+        mIncrementalManager = pm.mInjector.getIncrementalManager();
+        mApexManager = pm.mInjector.getApexManager();
+        mDexManager = pm.mInjector.getDexManager();
+        mArtManagerService = pm.mInjector.getArtManagerService();
+        mAppOpsManager = pm.mInjector.getSystemService(AppOpsManager.class);
+        mContext = pm.mInjector.getContext();
+        mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
+        mPackageAbiHelper = pm.mInjector.getAbiHelper();
+        mViewCompiler = pm.mInjector.getViewCompiler();
+        mIBackupManager = pm.mInjector.getIBackupManager();
     }
 
     InstallPackageHelper(PackageManagerService pm) {
         this(pm, new AppDataHelper(pm));
     }
 
-    InstallPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
-        mPm = pm;
-        mInjector = injector;
-        mAppDataHelper = new AppDataHelper(pm, mInjector);
-        mBroadcastHelper = new BroadcastHelper(injector);
-        mRemovePackageHelper = new RemovePackageHelper(pm);
-        mScanPackageHelper = new ScanPackageHelper(pm);
-    }
-
-    @GuardedBy("mPm.mLock")
-    public Map<String, ReconciledPackage> reconcilePackagesLocked(
-            final ReconcileRequest request, KeySetManagerService ksms,
-            PackageManagerServiceInjector injector)
-            throws ReconcileFailure {
-        final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
-
-        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
-
-        // make a copy of the existing set of packages so we can combine them with incoming packages
-        final ArrayMap<String, AndroidPackage> combinedPackages =
-                new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
-
-        combinedPackages.putAll(request.mAllPackages);
-
-        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
-                new ArrayMap<>();
-
-        for (String installPackageName : scannedPackages.keySet()) {
-            final ScanResult scanResult = scannedPackages.get(installPackageName);
-
-            // add / replace existing with incoming packages
-            combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
-                    scanResult.mRequest.mParsedPackage);
-
-            // in the first pass, we'll build up the set of incoming shared libraries
-            final List<SharedLibraryInfo> allowedSharedLibInfos =
-                    SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
-                            request.mSharedLibrarySource);
-            if (allowedSharedLibInfos != null) {
-                for (SharedLibraryInfo info : allowedSharedLibInfos) {
-                    if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
-                            incomingSharedLibraries, info)) {
-                        throw new ReconcileFailure("Shared Library " + info.getName()
-                                + " is being installed twice in this set!");
-                    }
-                }
-            }
-
-            // the following may be null if we're just reconciling on boot (and not during install)
-            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
-            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
-            final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
-            final boolean isInstall = installArgs != null;
-            if (isInstall && (res == null || prepareResult == null)) {
-                throw new ReconcileFailure("Reconcile arguments are not balanced for "
-                        + installPackageName + "!");
-            }
-
-            final DeletePackageAction deletePackageAction;
-            // we only want to try to delete for non system apps
-            if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
-                final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
-                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
-                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
-                        prepareResult.mOriginalPs, prepareResult.mDisabledPs,
-                        deleteFlags, null /* all users */);
-                if (deletePackageAction == null) {
-                    throw new ReconcileFailure(
-                            PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
-                            "May not delete " + installPackageName + " to replace");
-                }
-            } else {
-                deletePackageAction = null;
-            }
-
-            final int scanFlags = scanResult.mRequest.mScanFlags;
-            final int parseFlags = scanResult.mRequest.mParseFlags;
-            final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-
-            final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
-            final PackageSetting lastStaticSharedLibSetting =
-                    request.mLastStaticSharedLibSettings.get(installPackageName);
-            final PackageSetting signatureCheckPs =
-                    (prepareResult != null && lastStaticSharedLibSetting != null)
-                            ? lastStaticSharedLibSetting
-                            : scanResult.mPkgSetting;
-            boolean removeAppKeySetData = false;
-            boolean sharedUserSignaturesChanged = false;
-            SigningDetails signingDetails = null;
-            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
-                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                } else {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
-                                "Package " + parsedPackage.getPackageName()
-                                        + " upgrade keys do not match the previously installed"
-                                        + " version");
-                    } else {
-                        String msg = "System package " + parsedPackage.getPackageName()
-                                + " signature changed; retaining data.";
-                        PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-                    }
-                }
-                signingDetails = parsedPackage.getSigningDetails();
-            } else {
-                try {
-                    final Settings.VersionInfo versionInfo =
-                            request.mVersionInfos.get(installPackageName);
-                    final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
-                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
-                    final boolean isRollback = installArgs != null
-                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
-                    final boolean compatMatch = verifySignatures(signatureCheckPs,
-                            disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
-                            compareRecover, isRollback);
-                    // The new KeySets will be re-added later in the scanning process.
-                    if (compatMatch) {
-                        removeAppKeySetData = true;
-                    }
-                    // We just determined the app is signed correctly, so bring
-                    // over the latest parsed certs.
-                    signingDetails = parsedPackage.getSigningDetails();
-
-                    // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
-                    // details
-                    if (signatureCheckPs.getSharedUser() != null) {
-                        // Attempt to merge the existing lineage for the shared SigningDetails with
-                        // the lineage of the new package; if the shared SigningDetails are not
-                        // returned this indicates the new package added new signers to the lineage
-                        // and/or changed the capabilities of existing signers in the lineage.
-                        SigningDetails sharedSigningDetails =
-                                signatureCheckPs.getSharedUser().signatures.mSigningDetails;
-                        SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
-                                signingDetails);
-                        if (mergedDetails != sharedSigningDetails) {
-                            signatureCheckPs.getSharedUser().signatures.mSigningDetails =
-                                    mergedDetails;
-                        }
-                        if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
-                            signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
-                        }
-                    }
-                } catch (PackageManagerException e) {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        throw new ReconcileFailure(e);
-                    }
-                    signingDetails = parsedPackage.getSigningDetails();
-
-                    // If the system app is part of a shared user we allow that shared user to
-                    // change
-                    // signatures as well as part of an OTA. We still need to verify that the
-                    // signatures
-                    // are consistent within the shared user for a given boot, so only allow
-                    // updating
-                    // the signatures on the first package scanned for the shared user (i.e. if the
-                    // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
-                    if (signatureCheckPs.getSharedUser() != null) {
-                        final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
-                                .signatures.mSigningDetails.getSignatures();
-                        if (signatureCheckPs.getSharedUser().signaturesChanged != null
-                                && compareSignatures(sharedUserSignatures,
-                                parsedPackage.getSigningDetails().getSignatures())
-                                != PackageManager.SIGNATURE_MATCH) {
-                            if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
-                                // Mismatched signatures is an error and silently skipping system
-                                // packages will likely break the device in unforeseen ways.
-                                // However, we allow the device to boot anyway because, prior to Q,
-                                // vendors were not expecting the platform to crash in this
-                                // situation.
-                                // This WILL be a hard failure on any new API levels after Q.
-                                throw new ReconcileFailure(
-                                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
-                                        "Signature mismatch for shared user: "
-                                                + scanResult.mPkgSetting.getSharedUser());
-                            } else {
-                                // Treat mismatched signatures on system packages using a shared
-                                // UID as
-                                // fatal for the system overall, rather than just failing to install
-                                // whichever package happened to be scanned later.
-                                throw new IllegalStateException(
-                                        "Signature mismatch on system package "
-                                                + parsedPackage.getPackageName()
-                                                + " for shared user "
-                                                + scanResult.mPkgSetting.getSharedUser());
-                            }
-                        }
-
-                        sharedUserSignaturesChanged = true;
-                        signatureCheckPs.getSharedUser().signatures.mSigningDetails =
-                                parsedPackage.getSigningDetails();
-                        signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
-                    }
-                    // File a report about this.
-                    String msg = "System package " + parsedPackage.getPackageName()
-                            + " signature changed; retaining data.";
-                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-                } catch (IllegalArgumentException e) {
-                    // should never happen: certs matched when checking, but not when comparing
-                    // old to new for sharedUser
-                    throw new RuntimeException(
-                            "Signing certificates comparison made on incomparable signing details"
-                                    + " but somehow passed verifySignatures!", e);
-                }
-            }
-
-            result.put(installPackageName,
-                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
-                            res, request.mPreparedPackages.get(installPackageName), scanResult,
-                            deletePackageAction, allowedSharedLibInfos, signingDetails,
-                            sharedUserSignaturesChanged, removeAppKeySetData));
-        }
-
-        for (String installPackageName : scannedPackages.keySet()) {
-            // 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
-            // will take care of the system apps by updating all of their
-            // 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.
-            final ScanResult scanResult = scannedPackages.get(installPackageName);
-            if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
-                    || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
-                    != 0) {
-                continue;
-            }
-            try {
-                result.get(installPackageName).mCollectedSharedLibraryInfos =
-                        SharedLibraryHelper.collectSharedLibraryInfos(
-                                scanResult.mRequest.mParsedPackage,
-                                combinedPackages, request.mSharedLibrarySource,
-                                incomingSharedLibraries, injector.getCompatibility());
-
-            } catch (PackageManagerException e) {
-                throw new ReconcileFailure(e.error, e.getMessage());
-            }
-        }
-
-        return result;
-    }
-
     /**
      * Commits the package scan and modifies system state.
      * <p><em>WARNING:</em> The method may throw an exception in the middle
@@ -494,7 +264,13 @@
         if (request.mPkgSetting != null && request.mPkgSetting.getSharedUser() != null
                 && request.mPkgSetting.getSharedUser() != result.mPkgSetting.getSharedUser()) {
             // shared user changed, remove from old shared user
-            request.mPkgSetting.getSharedUser().removePackage(request.mPkgSetting);
+            final SharedUserSetting sus = request.mPkgSetting.getSharedUser();
+            sus.removePackage(request.mPkgSetting);
+            // Prune unused SharedUserSetting
+            if (mPm.mSettings.checkAndPruneSharedUserLPw(sus, false)) {
+                // Set the app ID in removed info for UID_REMOVED broadcasts
+                reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId = sus.userId;
+            }
         }
         if (result.mExistingSettingCopied) {
             pkgSetting = request.mPkgSetting;
@@ -672,7 +448,7 @@
             // Add the new setting to mPackages
             mPm.mPackages.put(pkg.getPackageName(), pkg);
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
-                mPm.mApexManager.registerApkInApex(pkg);
+                mApexManager.registerApkInApex(pkg);
             }
 
             // Add the package's KeySets to the global KeySetManagerService
@@ -723,27 +499,6 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    /**
-     * If the database version for this type of package (internal storage or
-     * external storage) is less than the version where package signatures
-     * were updated, return true.
-     */
-    public boolean isCompatSignatureUpdateNeeded(AndroidPackage pkg) {
-        return isCompatSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
-    }
-
-    public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
-        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
-    }
-
-    public boolean isRecoverSignatureUpdateNeeded(AndroidPackage pkg) {
-        return isRecoverSignatureUpdateNeeded(mPm.getSettingsVersionForPackage(pkg));
-    }
-
-    public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
-        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
-    }
-
     public int installExistingPackageAsUser(@Nullable String packageName, @UserIdInt int userId,
             @PackageManager.InstallFlags int installFlags,
             @PackageManager.InstallReason int installReason,
@@ -756,9 +511,9 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        if (mPm.mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED
-                && mPm.mContext.checkCallingOrSelfPermission(
+                && mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.INSTALL_EXISTING_PACKAGES)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Neither user " + callingUid + " nor current process has "
@@ -811,7 +566,8 @@
                     // upgrade app from instant to full; we don't allow app downgrade
                     installed = true;
                 }
-                mPm.setInstantAppForUser(mInjector, pkgSetting, userId, instantApp, fullApp);
+                ScanPackageUtils.setInstantAppForUser(mPm.mInjector, pkgSetting, userId, instantApp,
+                        fullApp);
             }
 
             if (installed) {
@@ -848,7 +604,7 @@
                             mPm.restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
                                     userId);
                             if (intentSender != null) {
-                                onRestoreComplete(res.mReturnCode, mPm.mContext, intentSender);
+                                onRestoreComplete(res.mReturnCode, mContext, intentSender);
                             }
                         });
                 restoreAndPostInstall(userId, res, postInstallData);
@@ -934,9 +690,7 @@
      * Returns whether the restore successfully completed.
      */
     private boolean performBackupManagerRestore(int userId, int token, PackageInstalledInfo res) {
-        IBackupManager bm = IBackupManager.Stub.asInterface(
-                ServiceManager.getService(Context.BACKUP_SERVICE));
-        if (bm != null) {
+        if (mIBackupManager != null) {
             // For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
             // in the BackupManager. USER_ALL is used in compatibility tests.
             if (userId == UserHandle.USER_ALL) {
@@ -947,8 +701,8 @@
             }
             Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "restore", token);
             try {
-                if (bm.isUserReadyForBackup(userId)) {
-                    bm.restoreAtInstallForUser(
+                if (mIBackupManager.isUserReadyForBackup(userId)) {
+                    mIBackupManager.restoreAtInstallForUser(
                             userId, res.mPkg.getPackageName(), token);
                 } else {
                     Slog.w(TAG, "User " + userId + " is not ready. Restore at install "
@@ -974,8 +728,6 @@
      */
     private boolean performRollbackManagerRestore(int userId, int token, PackageInstalledInfo res,
             PostInstallData data) {
-        RollbackManagerInternal rm = mInjector.getLocalService(RollbackManagerInternal.class);
-
         final String packageName = res.mPkg.getPackageName();
         final int[] allUsers = mPm.mUserManager.getUserIds();
         final int[] installedUsers;
@@ -1001,8 +753,8 @@
 
         if (ps != null && doSnapshotOrRestore) {
             final String seInfo = AndroidPackageUtils.getSeInfo(res.mPkg, ps);
-            rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
-                    appId, ceDataInode, seInfo, token);
+            mRollbackManager.snapshotAndRestoreUserData(packageName,
+                    UserHandle.toUserHandles(installedUsers), appId, ceDataInode, seInfo, token);
             return true;
         }
         return false;
@@ -1094,7 +846,7 @@
                                 + " got: " + apexes.length);
             }
             try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
-                mPm.mApexManager.installPackage(apexes[0], packageParser);
+                mApexManager.installPackage(apexes[0], packageParser);
             }
         } catch (PackageManagerException e) {
             request.mInstallResult.setError("APEX installation failed", e);
@@ -1171,7 +923,7 @@
                 installResults.put(packageName, request.mInstallResult);
                 installArgs.put(packageName, request.mArgs);
                 try {
-                    final ScanResult result = mScanPackageHelper.scanPackageTracedLI(
+                    final ScanResult result = scanPackageTracedLI(
                             prepareResult.mPackageToScan, prepareResult.mParseFlags,
                             prepareResult.mScanFlags, System.currentTimeMillis(),
                             request.mArgs.mUser, request.mArgs.mAbiOverride);
@@ -1184,6 +936,9 @@
                                         + " in multi-package install request.");
                         return;
                     }
+                    if (result.needsNewAppId()) {
+                        request.mInstallResult.mRemovedInfo.mAppIdChanging = true;
+                    }
                     createdAppId.put(packageName, optimisticallyRegisterAppId(result));
                     versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
                             mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
@@ -1214,7 +969,7 @@
                 Map<String, ReconciledPackage> reconciledPackages;
                 try {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
-                    reconciledPackages = reconcilePackagesLocked(
+                    reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             reconcileRequest, mPm.mSettings.getKeySetManagerService(),
                             mPm.mInjector);
                 } catch (ReconcileFailure e) {
@@ -1257,7 +1012,7 @@
                             .buildVerificationRootHashString(baseCodePath, splitCodePaths);
                     VerificationUtils.broadcastPackageVerified(verificationId, originUri,
                             PackageManager.VERIFICATION_ALLOW, rootHashString,
-                            args.mDataLoaderType, args.getUser(), mPm.mContext);
+                            args.mDataLoaderType, args.getUser(), mContext);
                 }
             } else {
                 for (ScanResult result : preparedScans.values()) {
@@ -1473,9 +1228,11 @@
                 } else {
                     try {
                         final boolean compareCompat =
-                                isCompatSignatureUpdateNeeded(parsedPackage);
+                                ReconcilePackageUtils.isCompatSignatureUpdateNeeded(
+                                        mPm.getSettingsVersionForPackage(parsedPackage));
                         final boolean compareRecover =
-                                isRecoverSignatureUpdateNeeded(parsedPackage);
+                                ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
+                                        mPm.getSettingsVersionForPackage(parsedPackage));
                         // We don't care about disabledPkgSetting on install for now.
                         final boolean compatMatch = verifySignatures(signatureCheckPs, null,
                                 parsedPackage.getSigningDetails(), compareCompat, compareRecover,
@@ -1677,9 +1434,9 @@
                 final String abiOverride = deriveAbiOverride(args.mAbiOverride);
                 boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
-                        derivedAbi = mPm.mInjector.getAbiHelper().derivePackageAbi(parsedPackage,
+                        derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
                         isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
-                        abiOverride, mPm.mAppLib32InstallDir);
+                        abiOverride, ScanPackageUtils.getAppLib32InstallDir());
                 derivedAbi.first.applyTo(parsedPackage);
                 derivedAbi.second.applyTo(parsedPackage);
             } catch (PackageManagerException pme) {
@@ -2322,12 +2079,19 @@
                     }
                 }
 
-                // It's implied that when a user requests installation, they want the app to be
-                // installed and enabled. (This does not apply to USER_ALL, which here means only
-                // install on users for which the app is already installed).
                 if (userId != UserHandle.USER_ALL) {
+                    // It's implied that when a user requests installation, they want the app to
+                    // be installed and enabled.
                     ps.setInstalled(true, userId);
                     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);
+                    }
                 }
 
                 mPm.mSettings.addInstallerPackageNames(ps.getInstallSource());
@@ -2370,8 +2134,8 @@
                 // that can be used for all the packages.
                 final String codePath = ps.getPathString();
                 if (IncrementalManager.isIncrementalPath(codePath)
-                        && mPm.mIncrementalManager != null) {
-                    mPm.mIncrementalManager.registerLoadingProgressCallback(codePath,
+                        && mIncrementalManager != null) {
+                    mIncrementalManager.registerLoadingProgressCallback(codePath,
                             new IncrementalProgressListener(ps.getPackageName(), mPm));
                 }
 
@@ -2432,17 +2196,16 @@
      */
     private void executePostCommitSteps(CommitRequest commitRequest) {
         final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
-        final AppDataHelper appDataHelper = new AppDataHelper(mPm);
         for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
             final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
                     & SCAN_AS_INSTANT_APP) != 0);
             final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
             final String packageName = pkg.getPackageName();
             final String codePath = pkg.getPath();
-            final boolean onIncremental = mPm.mIncrementalManager != null
+            final boolean onIncremental = mIncrementalManager != null
                     && isIncrementalPath(codePath);
             if (onIncremental) {
-                IncrementalStorage storage = mPm.mIncrementalManager.openStorage(codePath);
+                IncrementalStorage storage = mIncrementalManager.openStorage(codePath);
                 if (storage == null) {
                     throw new IllegalArgumentException(
                             "Install: null storage for incremental package " + packageName);
@@ -2454,28 +2217,28 @@
                 // Only set previousAppId if the app is migrating out of shared UID
                 previousAppId = reconciledPkg.mScanResult.mPreviousAppId;
             }
-            appDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
+            mAppDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId);
             if (reconciledPkg.mPrepareResult.mClearCodeCache) {
-                appDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
+                mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
                         FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                                 | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
             }
             if (reconciledPkg.mPrepareResult.mReplace) {
-                mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
+                mDexManager.notifyPackageUpdated(pkg.getPackageName(),
                         pkg.getBaseApkPath(), pkg.getSplitCodePaths());
             }
 
             // Prepare the application profiles for the new code paths.
             // This needs to be done before invoking dexopt so that any install-time profile
             // can be used for optimizations.
-            mPm.mArtManagerService.prepareAppProfiles(
+            mArtManagerService.prepareAppProfiles(
                     pkg,
                     mPm.resolveUserIds(reconciledPkg.mInstallArgs.mUser.getIdentifier()),
                     /* updateReferenceProfileContent= */ true);
 
             // Compute the compilation reason from the installation scenario.
             final int compilationReason =
-                    mPm.getDexManager().getCompilationReasonForInstallScenario(
+                    mDexManager.getCompilationReasonForInstallScenario(
                             reconciledPkg.mInstallArgs.mInstallScenario);
 
             // Construct the DexoptOptions early to see if we should skip running dexopt.
@@ -2523,7 +2286,7 @@
             //       path moved to SCENARIO_FAST.
             final boolean performDexopt =
                     (!instantApp || android.provider.Settings.Global.getInt(
-                            mPm.mContext.getContentResolver(),
+                            mContext.getContentResolver(),
                             android.provider.Settings.Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0)
                             && !pkg.isDebuggable()
                             && (!onIncremental)
@@ -2533,7 +2296,7 @@
                 // Compile the layout resources.
                 if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
-                    mPm.mViewCompiler.compileLayouts(pkg);
+                    mViewCompiler.compileLayouts(pkg);
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
 
@@ -2558,10 +2321,10 @@
 
                 realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
-                mPm.mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
+                mPackageDexOptimizer.performDexOpt(pkg, realPkgSetting,
                         null /* instructionSets */,
                         mPm.getOrCreateCompilerPackageStats(pkg),
-                        mPm.getDexManager().getPackageUseInfoOrDefault(packageName),
+                        mDexManager.getPackageUseInfoOrDefault(packageName),
                         dexoptOptions);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
             }
@@ -2574,7 +2337,8 @@
 
             notifyPackageChangeObserversOnUpdate(reconciledPkg);
         }
-        waitForNativeBinariesExtraction(incrementalStorages);
+        PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
+                incrementalStorages);
     }
 
     private void notifyPackageChangeObserversOnUpdate(ReconciledPackage reconciledPkg) {
@@ -2593,27 +2357,6 @@
         mPm.notifyPackageChangeObservers(pkgChangeEvent);
     }
 
-    private static void waitForNativeBinariesExtraction(
-            ArraySet<IncrementalStorage> incrementalStorages) {
-        if (incrementalStorages.isEmpty()) {
-            return;
-        }
-        try {
-            // Native library extraction may take very long time: each page could potentially
-            // wait for either 10s or 100ms (adb vs non-adb data loader), and that easily adds
-            // up to a full watchdog timeout of 1 min, killing the system after that. It doesn't
-            // make much sense as blocking here doesn't lock up the framework, but only blocks
-            // the installation session and the following ones.
-            Watchdog.getInstance().pauseWatchingCurrentThread("native_lib_extract");
-            for (int i = 0; i < incrementalStorages.size(); ++i) {
-                IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
-                storage.waitForNativeBinariesExtraction();
-            }
-        } finally {
-            Watchdog.getInstance().resumeWatchingCurrentThread("native_lib_extract");
-        }
-    }
-
     public int installLocationPolicy(PackageInfoLite pkgLite, int installFlags) {
         String packageName = pkgLite.packageName;
         int installLocation = pkgLite.installLocation;
@@ -2699,7 +2442,7 @@
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
                     try {
-                        checkDowngrade(dataOwnerPkg, pkgLite);
+                        PackageManagerServiceUtils.checkDowngrade(dataOwnerPkg, pkgLite);
                     } catch (PackageManagerException e) {
                         String errorMsg = "Downgrade detected: " + e.getMessage();
                         Slog.w(TAG, errorMsg);
@@ -2716,7 +2459,7 @@
             long requiredInstalledVersionCode, int installFlags) {
         String packageName = pkgLite.packageName;
 
-        final PackageInfo activePackage = mPm.mApexManager.getPackageInfo(packageName,
+        final PackageInfo activePackage = mApexManager.getPackageInfo(packageName,
                 ApexManager.MATCH_ACTIVE_PACKAGE);
         if (activePackage == null) {
             String errorMsg = "Attempting to install new APEX package " + packageName;
@@ -2749,41 +2492,6 @@
         return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
     }
 
-    /**
-     * Check and throw if the given before/after packages would be considered a
-     * downgrade.
-     */
-    private static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
-            throws PackageManagerException {
-        if (after.getLongVersionCode() < before.getLongVersionCode()) {
-            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                    "Update version code " + after.versionCode + " is older than current "
-                            + before.getLongVersionCode());
-        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
-            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
-                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                        "Update base revision code " + after.baseRevisionCode
-                                + " is older than current " + before.getBaseRevisionCode());
-            }
-
-            if (!ArrayUtils.isEmpty(after.splitNames)) {
-                for (int i = 0; i < after.splitNames.length; i++) {
-                    final String splitName = after.splitNames[i];
-                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
-                    if (j != -1) {
-                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
-                            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
-                                    "Update split " + splitName + " revision code "
-                                            + after.splitRevisionCodes[i]
-                                            + " is older than current "
-                                            + before.getSplitRevisionCodes()[j]);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
     int getUidForVerifier(VerifierInfo verifierInfo) {
         synchronized (mPm.mLock) {
             final AndroidPackage pkg = mPm.mPackages.get(verifierInfo.packageName);
@@ -2822,56 +2530,6 @@
         }
     }
 
-    /**
-     * Check whether or not package verification has been enabled.
-     *
-     * @return true if verification should be performed
-     */
-    boolean isVerificationEnabled(PackageInfoLite pkgInfoLite, int userId, int installFlags,
-            int installerUid) {
-        if (!DEFAULT_VERIFY_ENABLE) {
-            return false;
-        }
-
-        // Check if installing from ADB
-        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
-            if (mPm.isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
-                return true;
-            }
-            // Check if the developer wants to skip verification for ADB installs
-            if ((installFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0) {
-                synchronized (mPm.mLock) {
-                    if (mPm.mSettings.getPackageLPr(pkgInfoLite.packageName) == null) {
-                        // Always verify fresh install
-                        return true;
-                    }
-                }
-                // Only skip when apk is debuggable
-                return !pkgInfoLite.debuggable;
-            }
-            return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
-                    android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
-        }
-
-        // 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) {
-            if (mPm.mInstantAppInstallerActivity != null
-                    && mPm.mInstantAppInstallerActivity.packageName.equals(
-                    mPm.mRequiredVerifierPackage)) {
-                try {
-                    mPm.mInjector.getSystemService(AppOpsManager.class)
-                            .checkPackage(installerUid, mPm.mRequiredVerifierPackage);
-                    if (DEBUG_VERIFY) {
-                        Slog.i(TAG, "disable verification for instant app");
-                    }
-                    return false;
-                } catch (SecurityException ignore) { }
-            }
-        }
-        return true;
-    }
-
     public void sendPendingBroadcasts() {
         String[] packages;
         ArrayList<String>[] components;
@@ -2927,6 +2585,8 @@
         final int dataLoaderType = installArgs.mDataLoaderType;
         final boolean succeeded = res.mReturnCode == PackageManager.INSTALL_SUCCEEDED;
         final boolean update = res.mRemovedInfo != null && res.mRemovedInfo.mRemovedPackage != null;
+        final int previousAppId = (res.mRemovedInfo != null && res.mRemovedInfo.mAppIdChanging)
+                ? res.mRemovedInfo.mUid : Process.INVALID_UID;
         final String packageName = res.mName;
         final PackageStateInternal pkgSetting =
                 succeeded ? mPm.getPackageStateInternal(packageName) : null;
@@ -3023,9 +2683,12 @@
                         dataLoaderType);
 
                 // Send added for users that don't see the package for the first time
-                Bundle extras = new Bundle(1);
+                Bundle extras = new Bundle();
                 extras.putInt(Intent.EXTRA_UID, res.mUid);
-                if (update) {
+                if (previousAppId != Process.INVALID_UID) {
+                    extras.putBoolean(Intent.EXTRA_UID_CHANGING, true);
+                    extras.putInt(Intent.EXTRA_PREVIOUS_UID, previousAppId);
+                } else if (update) {
                     extras.putBoolean(Intent.EXTRA_REPLACING, true);
                 }
                 extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
@@ -3068,22 +2731,27 @@
 
                 // Send replaced for users that don't see the package for the first time
                 if (update) {
-                    mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                            packageName, extras, 0 /*flags*/,
-                            null /*targetPackage*/, null /*finishedReceiver*/,
-                            updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
-                            null);
-                    if (installerPackageName != null) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                installerPackageName, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
-                    }
-                    if (notifyVerifier) {
-                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
-                                extras, 0 /*flags*/,
-                                mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
-                                updateUserIds, instantUserIds, null /*broadcastAllowList*/, null);
+                    // Only send PACKAGE_REPLACED if appId has not changed
+                    if (previousAppId == Process.INVALID_UID) {
+                        mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                                packageName, extras, 0 /*flags*/,
+                                null /*targetPackage*/, null /*finishedReceiver*/,
+                                updateUserIds, instantUserIds, res.mRemovedInfo.mBroadcastAllowList,
+                                null);
+                        if (installerPackageName != null) {
+                            mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                                    extras, 0 /*flags*/,
+                                    installerPackageName, null /*finishedReceiver*/,
+                                    updateUserIds, instantUserIds, null /*broadcastAllowList*/,
+                                    null);
+                        }
+                        if (notifyVerifier) {
+                            mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                                    extras, 0 /*flags*/,
+                                    mPm.mRequiredVerifierPackage, null /*finishedReceiver*/,
+                                    updateUserIds, instantUserIds, null /*broadcastAllowList*/,
+                                    null);
+                        }
                     }
                     mPm.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                             null /*package*/, null /*extras*/, 0 /*flags*/,
@@ -3106,10 +2774,8 @@
                 // Send broadcast package appeared if external for all users
                 if (res.mPkg.isExternalStorage()) {
                     if (!update) {
-                        final StorageManager storage = mPm.mInjector.getSystemService(
-                                StorageManager.class);
                         VolumeInfo volume =
-                                storage.findVolumeByUuid(
+                                mStorageManager.findVolumeByUuid(
                                         StorageManager.convert(
                                                 res.mPkg.getVolumeUuid()).toString());
                         int packageExternalStorageType =
@@ -3191,7 +2857,7 @@
                 // There's a race currently where some install events may interleave with an
                 // uninstall. This can lead to package info being null (b/36642664).
                 if (info != null) {
-                    mPm.getDexManager().notifyPackageInstalled(info, userId);
+                    mDexManager.notifyPackageInstalled(info, userId);
                 }
             }
         }
@@ -3219,7 +2885,7 @@
      * @return the current "allow unknown sources" setting
      */
     private int getUnknownSourcesSettings() {
-        return android.provider.Settings.Secure.getIntForUser(mPm.mContext.getContentResolver(),
+        return android.provider.Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS,
                 -1, UserHandle.USER_SYSTEM);
     }
@@ -3348,7 +3014,7 @@
             mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
                     FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
                             | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
-            mPm.getDexManager().notifyPackageUpdated(pkg.getPackageName(),
+            mDexManager.notifyPackageUpdated(pkg.getPackageName(),
                     pkg.getBaseApkPath(), pkg.getSplitCodePaths());
         }
         return true;
@@ -3402,7 +3068,7 @@
                         packageName);
         int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName);
         if (ret == PackageManager.INSTALL_SUCCEEDED) {
-            ret = extractNativeBinaries(dstCodePath, packageName);
+            ret = PackageManagerServiceUtils.extractNativeBinaries(dstCodePath, packageName);
         }
         if (ret == PackageManager.INSTALL_SUCCEEDED) {
             // NOTE: During boot, we have to delay releasing cblocks for no other reason than
@@ -3415,7 +3081,7 @@
                 }
                 mPm.mReleaseOnSystemReady.add(dstCodePath);
             } else {
-                final ContentResolver resolver = mPm.mContext.getContentResolver();
+                final ContentResolver resolver = mContext.getContentResolver();
                 F2fsUtils.releaseCompressedBlocks(resolver, dstCodePath);
             }
         } else {
@@ -3429,33 +3095,15 @@
         return dstCodePath;
     }
 
-    private static int extractNativeBinaries(File dstCodePath, String packageName) {
-        final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
-        NativeLibraryHelper.Handle handle = null;
-        try {
-            handle = NativeLibraryHelper.Handle.create(dstCodePath);
-            return NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                    null /*abiOverride*/, false /*isIncremental*/);
-        } catch (IOException e) {
-            logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
-                    + "; pkg: " + packageName);
-            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
-        } finally {
-            IoUtils.closeQuietly(handle);
-        }
-    }
-
     /**
      * Tries to restore the disabled system package after an update has been deleted.
      */
-    @GuardedBy({"mPm.mLock", "mPm.mInstallLock"})
     public void restoreDisabledSystemPackageLIF(DeletePackageAction action,
-            PackageSetting deletedPs, @NonNull int[] allUserHandles,
-            @Nullable PackageRemovedInfo outInfo,
-            boolean writeSettings,
-            PackageSetting disabledPs)
-            throws SystemDeleteException {
-        // writer
+            @NonNull int[] allUserHandles, boolean writeSettings) throws SystemDeleteException {
+        final PackageSetting deletedPs = action.mDeletingPs;
+        final PackageRemovedInfo outInfo = action.mRemovedInfo;
+        final PackageSetting disabledPs = action.mDisabledPs;
+
         synchronized (mPm.mLock) {
             // NOTE: The system package always needs to be enabled; even if it's for
             // a compressed stub. If we don't, installing the system package fails
@@ -3464,25 +3112,27 @@
             // Reinstate the old system package
             mPm.mSettings.enableSystemPackageLPw(disabledPs.getPkg().getPackageName());
             // Remove any native libraries from the upgraded package.
-            removeNativeBinariesLI(deletedPs);
-        }
+            PackageManagerServiceUtils.removeNativeBinariesLI(deletedPs);
 
-        // Install the system package
-        if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
-        try {
-            installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
-                    outInfo == null ? null : outInfo.mOrigUsers, writeSettings);
-        } catch (PackageManagerException e) {
-            Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
-                    + e.getMessage());
-            // TODO(b/194319951): can we avoid this; throw would come from scan...
-            throw new SystemDeleteException(e);
-        } finally {
-            if (disabledPs.getPkg().isStub()) {
-                // We've re-installed the stub; make sure it's disabled here. If package was
-                // originally enabled, we'll install the compressed version of the application
-                // and re-enable it afterward.
-                disableStubPackage(action, deletedPs, allUserHandles);
+            // Install the system package
+            if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
+            try {
+                synchronized (mPm.mInstallLock) {
+                    installPackageFromSystemLIF(disabledPs.getPathString(), allUserHandles,
+                            outInfo == null ? null : outInfo.mOrigUsers, writeSettings);
+                }
+            } catch (PackageManagerException e) {
+                Slog.w(TAG, "Failed to restore system package:" + deletedPs.getPackageName() + ": "
+                        + e.getMessage());
+                // TODO(b/194319951): can we avoid this; throw would come from scan...
+                throw new SystemDeleteException(e);
+            } finally {
+                if (disabledPs.getPkg().isStub()) {
+                    // We've re-installed the stub; make sure it's disabled here. If package was
+                    // originally enabled, we'll install the compressed version of the application
+                    // and re-enable it afterward.
+                    disableStubPackage(action, deletedPs, allUserHandles);
+                }
             }
         }
     }
@@ -3505,12 +3155,6 @@
         }
     }
 
-    private static void removeNativeBinariesLI(PackageSetting ps) {
-        if (ps != null) {
-            NativeLibraryHelper.removeNativeBinariesLI(ps.getLegacyNativeLibraryPath());
-        }
-    }
-
     /**
      * Installs a package that's already on the system partition.
      */
@@ -3720,7 +3364,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installSystemPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
+    public void installPackagesFromDir(File scanDir, int parseFlags, int scanFlags,
             long currentTime, PackageParser2 packageParser, ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
@@ -3781,7 +3425,7 @@
             }
 
             if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
-                mPm.mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
+                mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);
             }
 
             // Delete invalid userdata apps
@@ -3864,7 +3508,7 @@
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
-        try (PackageParser2 pp = mInjector.getScanningPackageParser()) {
+        try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
             parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -3897,7 +3541,7 @@
             @PackageManagerService.ScanFlags int scanFlags, long currentTime,
             @Nullable UserHandle user) throws PackageManagerException {
 
-        final Pair<ScanResult, Boolean> scanResultPair = mScanPackageHelper.scanSystemPackageLI(
+        final Pair<ScanResult, Boolean> scanResultPair = scanSystemPackageLI(
                 parsedPackage, parseFlags, scanFlags, currentTime, user);
         final ScanResult scanResult = scanResultPair.first;
         boolean shouldHideSystemApp = scanResultPair.second;
@@ -3907,7 +3551,7 @@
                 try {
                     final String pkgName = scanResult.mPkgSetting.getPackageName();
                     final Map<String, ReconciledPackage> reconcileResult =
-                            reconcilePackagesLocked(
+                            ReconcilePackageUtils.reconcilePackages(
                                     new ReconcileRequest(
                                             Collections.singletonMap(pkgName, scanResult),
                                             mPm.mSharedLibraries,
@@ -3919,7 +3563,7 @@
                                             Collections.singletonMap(pkgName,
                                                     mPm.getSharedLibLatestVersionSetting(
                                                             scanResult))),
-                                    mPm.mSettings.getKeySetManagerService(), mInjector);
+                                    mPm.mSettings.getKeySetManagerService(), mPm.mInjector);
                     appIdCreated = optimisticallyRegisterAppId(scanResult);
                     commitReconciledScanResultLocked(
                             reconcileResult.get(pkgName), mPm.mUserManager.getUserIds());
@@ -3937,10 +3581,10 @@
                 mPm.mSettings.disableSystemPackageLPw(parsedPackage.getPackageName(), true);
             }
         }
-        if (mPm.mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
+        if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
             if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) {
                 // Continue monitoring loading progress of active incremental packages
-                mPm.mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(),
+                mIncrementalManager.registerLoadingProgressCallback(parsedPackage.getPath(),
                         new IncrementalProgressListener(parsedPackage.getPackageName(), mPm));
             }
         }
@@ -3978,5 +3622,743 @@
         }
     }
 
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
+            final @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
+        try {
+            return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
+                    cpuAbiOverride);
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
 
+    private ScanRequest prepareInitialScanRequest(@NonNull ParsedPackage parsedPackage,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags,
+            @Nullable UserHandle user, String cpuAbiOverride)
+            throws PackageManagerException {
+        final AndroidPackage platformPackage;
+        final String realPkgName;
+        final PackageSetting disabledPkgSetting;
+        final PackageSetting installedPkgSetting;
+        final PackageSetting originalPkgSetting;
+        final SharedUserSetting sharedUserSetting;
+
+        synchronized (mPm.mLock) {
+            platformPackage = mPm.getPlatformPackage();
+            final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
+                    AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
+            realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName);
+            if (realPkgName != null) {
+                ScanPackageUtils.ensurePackageRenamed(parsedPackage, renamedPkgName);
+            }
+            originalPkgSetting = getOriginalPackageLocked(parsedPackage, renamedPkgName);
+            installedPkgSetting = mPm.mSettings.getPackageLPr(parsedPackage.getPackageName());
+            if (mPm.mTransferredPackages.contains(parsedPackage.getPackageName())) {
+                Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+                        + " was transferred to another, but its .apk remains");
+            }
+            disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(
+                    parsedPackage.getPackageName());
+            sharedUserSetting = (parsedPackage.getSharedUserId() != null)
+                    ? mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
+                    0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
+                    : null;
+            if (DEBUG_PACKAGE_SCANNING
+                    && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
+                    && sharedUserSetting != null) {
+                Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
+                        + " (uid=" + sharedUserSetting.userId + "):"
+                        + " packages=" + sharedUserSetting.packages);
+            }
+        }
+
+        final boolean isPlatformPackage = platformPackage != null
+                && platformPackage.getPackageName().equals(parsedPackage.getPackageName());
+
+        return new ScanRequest(parsedPackage, sharedUserSetting,
+                installedPkgSetting == null ? null : installedPkgSetting.getPkg() /* oldPkg */,
+                installedPkgSetting /* packageSetting */,
+                disabledPkgSetting /* disabledPackageSetting */,
+                originalPkgSetting  /* originalPkgSetting */,
+                realPkgName, parseFlags, scanFlags, isPlatformPackage, user, cpuAbiOverride);
+    }
+
+    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
+    private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
+            final @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
+
+        final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
+                scanFlags, user, cpuAbiOverride);
+        final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
+        final PackageSetting disabledPkgSetting = initialScanRequest.mDisabledPkgSetting;
+
+        boolean isUpdatedSystemApp;
+        if (installedPkgSetting != null) {
+            isUpdatedSystemApp = installedPkgSetting.getPkgState().isUpdatedSystemApp();
+        } else {
+            isUpdatedSystemApp = disabledPkgSetting != null;
+        }
+
+        final int newScanFlags = adjustScanFlags(scanFlags, installedPkgSetting, disabledPkgSetting,
+                user, parsedPackage);
+        ScanPackageUtils.applyPolicy(parsedPackage, newScanFlags,
+                mPm.getPlatformPackage(), isUpdatedSystemApp);
+
+        synchronized (mPm.mLock) {
+            assertPackageIsValid(parsedPackage, parseFlags, newScanFlags);
+            final ScanRequest request = new ScanRequest(parsedPackage,
+                    initialScanRequest.mSharedUserSetting,
+                    initialScanRequest.mOldPkg, installedPkgSetting, disabledPkgSetting,
+                    initialScanRequest.mOriginalPkgSetting, initialScanRequest.mRealPkgName,
+                    parseFlags, scanFlags, initialScanRequest.mIsPlatformPackage, user,
+                    cpuAbiOverride);
+            return ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector, mPm.mFactoryTest,
+                    currentTime);
+        }
+    }
+
+    private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user) throws PackageManagerException {
+        final boolean scanSystemPartition =
+                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
+        final ScanRequest initialScanRequest = prepareInitialScanRequest(parsedPackage, parseFlags,
+                scanFlags, user, null);
+        final PackageSetting installedPkgSetting = initialScanRequest.mPkgSetting;
+        final PackageSetting originalPkgSetting = initialScanRequest.mOriginalPkgSetting;
+        final PackageSetting pkgSetting =
+                originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
+        final boolean pkgAlreadyExists = pkgSetting != null;
+        final String disabledPkgName = pkgAlreadyExists
+                ? pkgSetting.getPackageName() : parsedPackage.getPackageName();
+        final boolean isSystemPkgUpdated;
+        final boolean isUpgrade;
+        synchronized (mPm.mLock) {
+            isUpgrade = mPm.isDeviceUpgrading();
+            if (scanSystemPartition && !pkgAlreadyExists
+                    && mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName) != null) {
+                // The updated-package data for /system apk remains inconsistently
+                // after the package data for /data apk is lost accidentally.
+                // To recover it, enable /system apk and install it as non-updated system app.
+                Slog.w(TAG, "Inconsistent package setting of updated system app for "
+                        + disabledPkgName + ". To recover it, enable the system app "
+                        + "and install it as non-updated system app.");
+                mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
+            }
+            final PackageSetting disabledPkgSetting =
+                    mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
+            isSystemPkgUpdated = disabledPkgSetting != null;
+
+            if (DEBUG_INSTALL && isSystemPkgUpdated) {
+                Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
+            }
+
+            if (scanSystemPartition && isSystemPkgUpdated) {
+                // we're updating the disabled package, so, scan it as the package setting
+                final ScanRequest request = new ScanRequest(parsedPackage,
+                        initialScanRequest.mSharedUserSetting,
+                        null, disabledPkgSetting /* pkgSetting */,
+                        null /* disabledPkgSetting */, null /* originalPkgSetting */,
+                        null, parseFlags, scanFlags,
+                        initialScanRequest.mIsPlatformPackage, user, null);
+                ScanPackageUtils.applyPolicy(parsedPackage, scanFlags,
+                        mPm.getPlatformPackage(), true);
+                final ScanResult scanResult =
+                        ScanPackageUtils.scanPackageOnlyLI(request, mPm.mInjector,
+                                mPm.mFactoryTest, -1L);
+                if (scanResult.mExistingSettingCopied
+                        && scanResult.mRequest.mPkgSetting != null) {
+                    scanResult.mRequest.mPkgSetting.updateFrom(scanResult.mPkgSetting);
+                }
+            }
+        } // End of mLock
+
+        final boolean newPkgChangedPaths = pkgAlreadyExists
+                && !pkgSetting.getPathString().equals(parsedPackage.getPath());
+        final boolean newPkgVersionGreater = pkgAlreadyExists
+                && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode();
+        final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
+                && newPkgChangedPaths && newPkgVersionGreater;
+        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.
+            synchronized (mPm.mLock) {
+                // just remove the loaded entries from package lists
+                mPm.mPackages.remove(pkgSetting.getPackageName());
+            }
+
+            logCriticalInfo(Log.WARN,
+                    "System package updated;"
+                            + " name: " + pkgSetting.getPackageName()
+                            + "; " + pkgSetting.getVersionCode() + " --> "
+                            + parsedPackage.getLongVersionCode()
+                            + "; " + pkgSetting.getPathString()
+                            + " --> " + parsedPackage.getPath());
+
+            final InstallArgs args = new FileInstallArgs(
+                    pkgSetting.getPathString(), getAppDexInstructionSets(
+                    pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm);
+            args.cleanUpResourcesLI();
+            synchronized (mPm.mLock) {
+                mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
+            }
+        }
+
+        // The version of the application on the /system partition is less than or
+        // equal to the version on the /data partition. Throw an exception and use
+        // the application already installed on the /data partition.
+        if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
+            // In the case of a skipped package, commitReconciledScanResultLocked is not called to
+            // add the object to the "live" data structures, so this is the final mutation step
+            // for the package. Which means it needs to be finalized here to cache derived fields.
+            // This is relevant for cases where the disabled system package is used for flags or
+            // other metadata.
+            parsedPackage.hideAsFinal();
+            throw new PackageManagerException(Log.WARN, "Package " + parsedPackage.getPackageName()
+                    + " at " + parsedPackage.getPath() + " ignored: updated version "
+                    + (pkgAlreadyExists ? String.valueOf(pkgSetting.getVersionCode()) : "unknown")
+                    + " better than this " + parsedPackage.getLongVersionCode());
+        }
+
+        // Verify certificates against what was last scanned. Force re-collecting certificate in two
+        // special cases:
+        // 1) when scanning system, force re-collect only if system is upgrading.
+        // 2) when scanning /data, force re-collect only if the app is privileged (updated from
+        // preinstall, or treated as privileged, e.g. due to shared user ID).
+        final boolean forceCollect = scanSystemPartition ? isUpgrade
+                : PackageManagerServiceUtils.isApkVerificationForced(pkgSetting);
+        if (DEBUG_VERIFY && forceCollect) {
+            Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName());
+        }
+
+        // Full APK verification can be skipped during certificate collection, only if the file is
+        // in verified partition, or can be verified on access (when apk verity is enabled). In both
+        // cases, only data in Signing Block is verified instead of the whole file.
+        final boolean skipVerify = scanSystemPartition
+                || (forceCollect && canSkipForcedPackageVerification(parsedPackage));
+        ScanPackageUtils.collectCertificatesLI(pkgSetting, parsedPackage,
+                mPm.getSettingsVersionForPackage(parsedPackage), forceCollect, skipVerify,
+                mPm.isPreNMR1Upgrade());
+
+        // Reset profile if the application version is changed
+        maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage);
+
+        /*
+         * A new system app appeared, but we already had a non-system one of the
+         * same name installed earlier.
+         */
+        boolean shouldHideSystemApp = false;
+        // A new application appeared on /system, but, we already have a copy of
+        // the application installed on /data.
+        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
+                && !pkgSetting.isSystem()) {
+
+            if (!parsedPackage.getSigningDetails()
+                    .checkCapability(pkgSetting.getSigningDetails(),
+                            SigningDetails.CertCapabilities.INSTALLED_DATA)
+                    && !pkgSetting.getSigningDetails().checkCapability(
+                    parsedPackage.getSigningDetails(),
+                    SigningDetails.CertCapabilities.ROLLBACK)) {
+                logCriticalInfo(Log.WARN,
+                        "System package signature mismatch;"
+                                + " name: " + pkgSetting.getPackageName());
+                try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
+                        parsedPackage.getPackageName(),
+                        "scanPackageInternalLI")) {
+                    DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
+                    deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
+                            mPm.mUserManager.getUserIds(), 0, null, false);
+                }
+            } else if (newPkgVersionGreater) {
+                // The application on /system is newer than the application on /data.
+                // Simply remove the application on /data [keeping application data]
+                // and replace it with the version on /system.
+                logCriticalInfo(Log.WARN,
+                        "System package enabled;"
+                                + " name: " + pkgSetting.getPackageName()
+                                + "; " + pkgSetting.getVersionCode() + " --> "
+                                + parsedPackage.getLongVersionCode()
+                                + "; " + pkgSetting.getPathString() + " --> "
+                                + parsedPackage.getPath());
+                InstallArgs args = new FileInstallArgs(
+                        pkgSetting.getPathString(), getAppDexInstructionSets(
+                        pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()),
+                        mPm);
+                synchronized (mPm.mInstallLock) {
+                    args.cleanUpResourcesLI();
+                }
+            } else {
+                // The application on /system is older than the application on /data. Hide
+                // the application on /system and the version on /data will be scanned later
+                // and re-added like an update.
+                shouldHideSystemApp = true;
+                logCriticalInfo(Log.INFO,
+                        "System package disabled;"
+                                + " name: " + pkgSetting.getPackageName()
+                                + "; old: " + pkgSetting.getPathString() + " @ "
+                                + pkgSetting.getVersionCode()
+                                + "; new: " + parsedPackage.getPath() + " @ "
+                                + parsedPackage.getPath());
+            }
+        }
+
+        final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
+                scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
+        return new Pair<>(scanResult, shouldHideSystemApp);
+    }
+
+    /**
+     * Returns if forced apk verification can be skipped for the whole package, including splits.
+     */
+    private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
+        final String packageName = pkg.getPackageName();
+        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
+            return false;
+        }
+        // TODO: Allow base and splits to be verified individually.
+        String[] splitCodePaths = pkg.getSplitCodePaths();
+        if (!ArrayUtils.isEmpty(splitCodePaths)) {
+            for (int i = 0; i < splitCodePaths.length; i++) {
+                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
+     * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
+     * match one in a trusted source, and should be done separately.
+     */
+    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
+        if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
+            return VerityUtils.hasFsverity(apkPath);
+        }
+
+        try {
+            final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
+            if (rootHashObserved == null) {
+                return false;  // APK does not contain Merkle tree root hash.
+            }
+            synchronized (mPm.mInstallLock) {
+                // Returns whether the observed root hash matches what kernel has.
+                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
+                        rootHashObserved);
+                return true;
+            }
+        } catch (Installer.InstallerException | IOException | DigestException
+                | NoSuchAlgorithmException e) {
+            Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Clear the package profile if this was an upgrade and the package
+     * version was updated.
+     */
+    private void maybeClearProfilesForUpgradesLI(
+            @Nullable PackageSetting originalPkgSetting,
+            @NonNull AndroidPackage pkg) {
+        if (originalPkgSetting == null || !mPm.isDeviceUpgrading()) {
+            return;
+        }
+        if (originalPkgSetting.getVersionCode() == pkg.getLongVersionCode()) {
+            return;
+        }
+
+        mAppDataHelper.clearAppProfilesLIF(pkg);
+        if (DEBUG_INSTALL) {
+            Slog.d(TAG, originalPkgSetting.getPackageName()
+                    + " clear profile due to version change "
+                    + originalPkgSetting.getVersionCode() + " != "
+                    + pkg.getLongVersionCode());
+        }
+    }
+
+    /**
+     * Returns the original package setting.
+     * <p>A package can migrate its name during an update. In this scenario, a package
+     * designates a set of names that it considers as one of its original names.
+     * <p>An original package must be signed identically and it must have the same
+     * shared user [if any].
+     */
+    @GuardedBy("mPm.mLock")
+    @Nullable
+    private PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg,
+            @Nullable String renamedPkgName) {
+        if (ScanPackageUtils.isPackageRenamed(pkg, renamedPkgName)) {
+            return null;
+        }
+        for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) {
+            final PackageSetting originalPs =
+                    mPm.mSettings.getPackageLPr(pkg.getOriginalPackages().get(i));
+            if (originalPs != null) {
+                // the package is already installed under its original name...
+                // but, should we use it?
+                if (!verifyPackageUpdateLPr(originalPs, pkg)) {
+                    // the new package is incompatible with the original
+                    continue;
+                } else if (originalPs.getSharedUser() != null) {
+                    if (!originalPs.getSharedUser().name.equals(pkg.getSharedUserId())) {
+                        // the shared user id is incompatible with the original
+                        Slog.w(TAG, "Unable to migrate data from " + originalPs.getPackageName()
+                                + " to " + pkg.getPackageName() + ": old uid "
+                                + originalPs.getSharedUser().name
+                                + " differs from " + pkg.getSharedUserId());
+                        continue;
+                    }
+                    // TODO: Add case when shared user id is added [b/28144775]
+                } else {
+                    if (DEBUG_UPGRADE) {
+                        Log.v(TAG, "Renaming new package "
+                                + pkg.getPackageName() + " to old name "
+                                + originalPs.getPackageName());
+                    }
+                }
+                return originalPs;
+            }
+        }
+        return null;
+    }
+
+    @GuardedBy("mPm.mLock")
+    private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, AndroidPackage newPkg) {
+        if ((oldPkg.getFlags() & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
+                    + " to " + newPkg.getPackageName()
+                    + ": old package not in system partition");
+            return false;
+        } else if (mPm.mPackages.get(oldPkg.getPackageName()) != null) {
+            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
+                    + " to " + newPkg.getPackageName()
+                    + ": old package still exists");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Asserts the parsed package is valid according to the given policy. If the
+     * package is invalid, for whatever reason, throws {@link PackageManagerException}.
+     * <p>
+     * Implementation detail: This method must NOT have any side effects. It would
+     * ideally be static, but, it requires locks to read system state.
+     *
+     * @throws PackageManagerException If the package fails any of the validation checks
+     */
+    private void assertPackageIsValid(AndroidPackage pkg,
+            final @ParsingPackageUtils.ParseFlags int parseFlags,
+            final @PackageManagerService.ScanFlags int scanFlags)
+            throws PackageManagerException {
+        if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) {
+            ScanPackageUtils.assertCodePolicy(pkg);
+        }
+
+        if (pkg.getPath() == null) {
+            // Bail out. The resource and code paths haven't been set.
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Code and resource paths haven't been set correctly");
+        }
+
+        // Check that there is an APEX package with the same name only during install/first boot
+        // after OTA.
+        final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0;
+        final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+        if ((isUserInstall || isFirstBootOrUpgrade)
+                && mApexManager.isApexPackage(pkg.getPackageName())) {
+            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                    pkg.getPackageName()
+                            + " is an APEX package and can't be installed as an APK.");
+        }
+
+        // Make sure we're not adding any bogus keyset info
+        final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
+        ksms.assertScannedPackageValid(pkg);
+
+        synchronized (mPm.mLock) {
+            // The special "android" package can only be defined once
+            if (pkg.getPackageName().equals("android")) {
+                if (mPm.getCoreAndroidApplication() != null) {
+                    Slog.w(TAG, "*************************************************");
+                    Slog.w(TAG, "Core android package being redefined.  Skipping.");
+                    Slog.w(TAG, " codePath=" + pkg.getPath());
+                    Slog.w(TAG, "*************************************************");
+                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                            "Core android package being redefined.  Skipping.");
+                }
+            }
+
+            // A package name must be unique; don't allow duplicates
+            if ((scanFlags & SCAN_NEW_INSTALL) == 0
+                    && mPm.mPackages.containsKey(pkg.getPackageName())) {
+                throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+                        "Application package " + pkg.getPackageName()
+                                + " already installed.  Skipping duplicate.");
+            }
+
+            if (pkg.isStaticSharedLibrary()) {
+                // Static libs have a synthetic package name containing the version
+                // but we still want the base name to be unique.
+                if ((scanFlags & SCAN_NEW_INSTALL) == 0
+                        && mPm.mPackages.containsKey(pkg.getManifestPackageName())) {
+                    throw new PackageManagerException(
+                            "Duplicate static shared lib provider package");
+                }
+                ScanPackageUtils.assertStaticSharedLibraryIsValid(pkg, scanFlags);
+                assertStaticSharedLibraryVersionCodeIsValid(pkg);
+            }
+
+            // If we're only installing presumed-existing packages, require that the
+            // scanned APK is both already known and at the path previously established
+            // for it.  Previously unknown packages we pick up normally, but if we have an
+            // a priori expectation about this package's install presence, enforce it.
+            // With a singular exception for new system packages. When an OTA contains
+            // a new system package, we allow the codepath to change from a system location
+            // to the user-installed location. If we don't allow this change, any newer,
+            // user-installed version of the application will be ignored.
+            if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
+                if (mPm.isExpectingBetter(pkg.getPackageName())) {
+                    Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package "
+                            + pkg.getPackageName());
+                } else {
+                    PackageSetting known = mPm.mSettings.getPackageLPr(pkg.getPackageName());
+                    if (known != null) {
+                        if (DEBUG_PACKAGE_SCANNING) {
+                            Log.d(TAG, "Examining " + pkg.getPath()
+                                    + " and requiring known path " + known.getPathString());
+                        }
+                        if (!pkg.getPath().equals(known.getPathString())) {
+                            throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
+                                    "Application package " + pkg.getPackageName()
+                                            + " found at " + pkg.getPath()
+                                            + " but expected at " + known.getPathString()
+                                            + "; ignoring.");
+                        }
+                    } else {
+                        throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                                "Application package " + pkg.getPackageName()
+                                        + " not found; ignoring.");
+                    }
+                }
+            }
+
+            // Verify that this new package doesn't have any content providers
+            // that conflict with existing packages.  Only do this if the
+            // package isn't already installed, since we don't want to break
+            // things that are installed.
+            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
+                mPm.mComponentResolver.assertProvidersNotDefined(pkg);
+            }
+
+            // If this package has defined explicit processes, then ensure that these are
+            // the only processes used by its components.
+            ScanPackageUtils.assertProcessesAreValid(pkg);
+
+            // Verify that packages sharing a user with a privileged app are marked as privileged.
+            assertPackageWithSharedUserIdIsPrivileged(pkg);
+
+            // Apply policies specific for runtime resource overlays (RROs).
+            if (pkg.getOverlayTarget() != null) {
+                assertOverlayIsValid(pkg, parseFlags, scanFlags);
+            }
+
+            // If the package is not on a system partition ensure it is signed with at least the
+            // minimum signature scheme version required for its target SDK.
+            ScanPackageUtils.assertMinSignatureSchemeIsValid(pkg, parseFlags);
+        }
+    }
+
+    private void assertStaticSharedLibraryVersionCodeIsValid(AndroidPackage pkg)
+            throws PackageManagerException {
+        // The version codes must be ordered as lib versions
+        long minVersionCode = Long.MIN_VALUE;
+        long maxVersionCode = Long.MAX_VALUE;
+
+        WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(
+                pkg.getStaticSharedLibName());
+        if (versionedLib != null) {
+            final int versionCount = versionedLib.size();
+            for (int i = 0; i < versionCount; i++) {
+                SharedLibraryInfo libInfo = versionedLib.valueAt(i);
+                final long libVersionCode = libInfo.getDeclaringPackage()
+                        .getLongVersionCode();
+                if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
+                    minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
+                } else if (libInfo.getLongVersion()
+                        > pkg.getStaticSharedLibVersion()) {
+                    maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
+                } else {
+                    minVersionCode = maxVersionCode = libVersionCode;
+                    break;
+                }
+            }
+        }
+        if (pkg.getLongVersionCode() < minVersionCode
+                || pkg.getLongVersionCode() > maxVersionCode) {
+            throw new PackageManagerException("Static shared"
+                    + " lib version codes must be ordered as lib versions");
+        }
+    }
+
+    private void assertOverlayIsValid(AndroidPackage pkg,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException {
+        // System overlays have some restrictions on their use of the 'static' state.
+        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+            // We are scanning a system overlay. This can be the first scan of the
+            // system/vendor/oem partition, or an update to the system overlay.
+            if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                // This must be an update to a system overlay. Immutable overlays cannot be
+                // upgraded.
+                if (!mPm.isOverlayMutable(pkg.getPackageName())) {
+                    throw new PackageManagerException("Overlay "
+                            + pkg.getPackageName()
+                            + " is static and cannot be upgraded.");
+                }
+            } else {
+                if ((scanFlags & SCAN_AS_VENDOR) != 0) {
+                    if (pkg.getTargetSdkVersion() < ScanPackageUtils.getVendorPartitionVersion()) {
+                        Slog.w(TAG, "System overlay " + pkg.getPackageName()
+                                + " targets an SDK below the required SDK level of vendor"
+                                + " overlays ("
+                                + ScanPackageUtils.getVendorPartitionVersion()
+                                + ")."
+                                + " This will become an install error in a future release");
+                    }
+                } else if (pkg.getTargetSdkVersion() < Build.VERSION.SDK_INT) {
+                    Slog.w(TAG, "System overlay " + pkg.getPackageName()
+                            + " targets an SDK below the required SDK level of system"
+                            + " overlays (" + Build.VERSION.SDK_INT + ")."
+                            + " This will become an install error in a future release");
+                }
+            }
+        } else {
+            // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
+            // signed with the platform certificate. Check this in increasing order of
+            // computational cost.
+            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.Q) {
+                final PackageSetting platformPkgSetting =
+                        mPm.mSettings.getPackageLPr("android");
+                if (!comparePackageSignatures(platformPkgSetting,
+                        pkg.getSigningDetails().getSignatures())) {
+                    throw new PackageManagerException("Overlay "
+                            + pkg.getPackageName()
+                            + " must target Q or later, "
+                            + "or be signed with the platform certificate");
+                }
+            }
+
+            // A non-preloaded overlay package, without <overlay android:targetName>, will
+            // only be used if it is signed with the same certificate as its target OR if
+            // it is signed with the same certificate as a reference package declared
+            // in 'overlay-config-signature' tag of SystemConfig.
+            // If the target is already installed or 'overlay-config-signature' tag in
+            // SystemConfig is set, check this here to augment the last line of defense
+            // which is OMS.
+            if (pkg.getOverlayTargetOverlayableName() == null) {
+                final PackageSetting targetPkgSetting =
+                        mPm.mSettings.getPackageLPr(pkg.getOverlayTarget());
+                if (targetPkgSetting != null) {
+                    if (!comparePackageSignatures(targetPkgSetting,
+                            pkg.getSigningDetails().getSignatures())) {
+                        // check reference signature
+                        if (mPm.mOverlayConfigSignaturePackage == null) {
+                            throw new PackageManagerException("Overlay "
+                                    + pkg.getPackageName() + " and target "
+                                    + pkg.getOverlayTarget() + " signed with"
+                                    + " different certificates, and the overlay lacks"
+                                    + " <overlay android:targetName>");
+                        }
+                        final PackageSetting refPkgSetting =
+                                mPm.mSettings.getPackageLPr(
+                                        mPm.mOverlayConfigSignaturePackage);
+                        if (!comparePackageSignatures(refPkgSetting,
+                                pkg.getSigningDetails().getSignatures())) {
+                            throw new PackageManagerException("Overlay "
+                                    + pkg.getPackageName() + " signed with a different "
+                                    + "certificate than both the reference package and "
+                                    + "target " + pkg.getOverlayTarget() + ", and the "
+                                    + "overlay lacks <overlay android:targetName>");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg)
+            throws PackageManagerException {
+        if (!pkg.isPrivileged() && (pkg.getSharedUserId() != null)) {
+            SharedUserSetting sharedUserSetting = null;
+            try {
+                sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(),
+                        0, 0, false);
+            } catch (PackageManagerException ignore) {
+            }
+            if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
+                // Exempt SharedUsers signed with the platform key.
+                PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
+                if (!comparePackageSignatures(platformPkgSetting,
+                        pkg.getSigningDetails().getSignatures())) {
+                    throw new PackageManagerException("Apps that share a user with a "
+                            + "privileged app must themselves be marked as privileged. "
+                            + pkg.getPackageName() + " shares privileged user "
+                            + pkg.getSharedUserId() + ".");
+                }
+            }
+        }
+    }
+
+    private @PackageManagerService.ScanFlags int adjustScanFlags(
+            @PackageManagerService.ScanFlags int scanFlags,
+            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user,
+            AndroidPackage pkg) {
+        scanFlags = ScanPackageUtils.adjustScanFlagsWithPackageSetting(scanFlags, pkgSetting,
+                disabledPkgSetting, user);
+
+        // Exception for privileged apps that share a user with a priv-app.
+        final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
+                && ScanPackageUtils.getVendorPartitionVersion() < 28;
+        if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
+                && !pkg.isPrivileged()
+                && (pkg.getSharedUserId() != null)
+                && !skipVendorPrivilegeScan) {
+            SharedUserSetting sharedUserSetting = null;
+            synchronized (mPm.mLock) {
+                try {
+                    sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0,
+                            0, false);
+                } catch (PackageManagerException ignore) {
+                }
+                if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
+                    // Exempt SharedUsers signed with the platform key.
+                    // TODO(b/72378145) Fix this exemption. Force signature apps
+                    // to allowlist their privileged permissions just like other
+                    // priv-apps.
+                    PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
+                    if ((compareSignatures(
+                            platformPkgSetting.getSigningDetails().getSignatures(),
+                            pkg.getSigningDetails().getSignatures())
+                            != PackageManager.SIGNATURE_MATCH)) {
+                        scanFlags |= SCAN_AS_PRIVILEGED;
+                    }
+                }
+            }
+        }
+
+        return scanFlags;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9c11cd4..6e6773f 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -121,6 +121,7 @@
     public void onStart() {
         publishBinderService(Context.LAUNCHER_APPS_SERVICE, mLauncherAppsImpl);
         mLauncherAppsImpl.registerLoadingProgressForIncrementalApps();
+        LocalServices.addService(LauncherAppsServiceInternal.class, mLauncherAppsImpl.mInternal);
     }
 
     static class BroadcastCookie {
@@ -137,6 +138,17 @@
         }
     }
 
+    /**
+     * Local system service interface.
+     * @hide Only for use within system server
+     */
+    public abstract static class LauncherAppsServiceInternal {
+        /** Same as startShortcut except supports forwarding of caller uid/pid. */
+        public abstract boolean startShortcut(int callerUid, int callerPid, String callingPackage,
+                String packageName, String featureId, String shortcutId, Rect sourceBounds,
+                Bundle startActivityOptions, int targetUserId);
+    }
+
     @VisibleForTesting
     static class LauncherAppsImpl extends ILauncherApps.Stub {
         private static final boolean DEBUG = false;
@@ -168,6 +180,8 @@
 
         private PackageInstallerService mPackageInstallerService;
 
+        final LauncherAppsServiceInternal mInternal;
+
         public LauncherAppsImpl(Context context) {
             mContext = context;
             mIPM = AppGlobals.getPackageManager();
@@ -189,6 +203,7 @@
             mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
             mCallbackHandler = BackgroundThread.getHandler();
             mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+            mInternal = new LocalService();
         }
 
         @VisibleForTesting
@@ -359,11 +374,15 @@
          * group.
          */
         private boolean canAccessProfile(int targetUserId, String message) {
-            final int callingUserId = injectCallingUserId();
+            return canAccessProfile(injectBinderCallingUid(), injectCallingUserId(),
+                    injectBinderCallingPid(), targetUserId, message);
+        }
+
+        private boolean canAccessProfile(int callingUid, int callingUserId, int callingPid,
+                int targetUserId, String message) {
 
             if (targetUserId == callingUserId) return true;
-            if (injectHasInteractAcrossUsersFullPermission(injectBinderCallingPid(),
-                    injectBinderCallingUid())) {
+            if (injectHasInteractAcrossUsersFullPermission(callingPid, callingUid)) {
                 return true;
             }
 
@@ -379,25 +398,29 @@
                 injectRestoreCallingIdentity(ident);
             }
 
-            return mUserManagerInternal.isProfileAccessible(injectCallingUserId(), targetUserId,
+            return mUserManagerInternal.isProfileAccessible(callingUserId, targetUserId,
                     message, true);
         }
 
+        private void verifyCallingPackage(String callingPackage) {
+            verifyCallingPackage(callingPackage, injectBinderCallingUid());
+        }
+
         @VisibleForTesting // We override it in unit tests
-        void verifyCallingPackage(String callingPackage) {
+        void verifyCallingPackage(String callingPackage, int callerUid) {
             int packageUid = -1;
             try {
                 packageUid = mIPM.getPackageUid(callingPackage,
                         PackageManager.MATCH_DIRECT_BOOT_AWARE
                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                                 | PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                        UserHandle.getUserId(getCallingUid()));
+                        UserHandle.getUserId(callerUid));
             } catch (RemoteException ignore) {
             }
             if (packageUid < 0) {
                 Log.e(TAG, "Package not found: " + callingPackage);
             }
-            if (packageUid != injectBinderCallingUid()) {
+            if (packageUid != callerUid) {
                 throw new SecurityException("Calling package name mismatch");
             }
         }
@@ -797,9 +820,15 @@
         }
 
         private void ensureShortcutPermission(@NonNull String callingPackage) {
-            verifyCallingPackage(callingPackage);
-            if (!mShortcutServiceInternal.hasShortcutHostPermission(getCallingUserId(),
-                    callingPackage, injectBinderCallingPid(), injectBinderCallingUid())) {
+            ensureShortcutPermission(injectBinderCallingUid(), injectBinderCallingPid(),
+                    callingPackage);
+        }
+
+        private void ensureShortcutPermission(int callerUid, int callerPid,
+                @NonNull String callingPackage) {
+            verifyCallingPackage(callingPackage, callerUid);
+            if (!mShortcutServiceInternal.hasShortcutHostPermission(UserHandle.getUserId(callerUid),
+                    callingPackage, callerPid, callerUid)) {
                 throw new SecurityException("Caller can't access shortcut information");
             }
         }
@@ -989,20 +1018,28 @@
         public boolean startShortcut(String callingPackage, String packageName, String featureId,
                 String shortcutId, Rect sourceBounds, Bundle startActivityOptions,
                 int targetUserId) {
-            verifyCallingPackage(callingPackage);
+            return startShortcutInner(injectBinderCallingUid(), injectBinderCallingPid(),
+                    injectCallingUserId(), callingPackage, packageName, featureId, shortcutId,
+                    sourceBounds, startActivityOptions, targetUserId);
+        }
+
+        private boolean startShortcutInner(int callerUid, int callerPid, int callingUserId,
+                String callingPackage, String packageName, String featureId, String shortcutId,
+                Rect sourceBounds, Bundle startActivityOptions, int targetUserId) {
+            verifyCallingPackage(callingPackage, callerUid);
             if (!canAccessProfile(targetUserId, "Cannot start activity")) {
                 return false;
             }
 
             // Even without the permission, pinned shortcuts are always launchable.
-            if (!mShortcutServiceInternal.isPinnedByCaller(getCallingUserId(),
+            if (!mShortcutServiceInternal.isPinnedByCaller(callingUserId,
                     callingPackage, packageName, shortcutId, targetUserId)) {
-                ensureShortcutPermission(callingPackage);
+                ensureShortcutPermission(callerUid, callerPid, callingPackage);
             }
 
             final Intent[] intents = mShortcutServiceInternal.createShortcutIntents(
-                    getCallingUserId(), callingPackage, packageName, shortcutId, targetUserId,
-                    injectBinderCallingPid(), injectBinderCallingUid());
+                    callingUserId, callingPackage, packageName, shortcutId, targetUserId,
+                    callerPid, callerUid);
             if (intents == null || intents.length == 0) {
                 return false;
             }
@@ -1023,7 +1060,7 @@
 
             // Replace theme for splash screen
             final String splashScreenThemeResName =
-                    mShortcutServiceInternal.getShortcutStartingThemeResName(getCallingUserId(),
+                    mShortcutServiceInternal.getShortcutStartingThemeResName(callingUserId,
                             callingPackage, packageName, shortcutId, targetUserId);
             if (splashScreenThemeResName != null && !splashScreenThemeResName.isEmpty()) {
                 if (startActivityOptions == null) {
@@ -1809,5 +1846,16 @@
                 }
             }
         }
+
+        final class LocalService extends LauncherAppsServiceInternal {
+            @Override
+            public boolean startShortcut(int callerUid, int callerPid, String callingPackage,
+                    String packageName, String featureId, String shortcutId, Rect sourceBounds,
+                    Bundle startActivityOptions, int targetUserId) {
+                return LauncherAppsImpl.this.startShortcutInner(callerUid, callerPid,
+                        UserHandle.getUserId(callerUid), callingPackage, packageName, featureId,
+                        shortcutId, sourceBounds, startActivityOptions, targetUserId);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 1a9c7a9..217bc23 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -22,7 +22,6 @@
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
 import static com.android.server.pm.PackageManagerService.DEFAULT_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD;
-import static com.android.server.pm.PackageManagerService.DEFAULT_VERIFICATION_RESPONSE;
 import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_INSTALL_OBSERVER;
 import static com.android.server.pm.PackageManagerService.DEFERRED_NO_KILL_POST_DELETE;
 import static com.android.server.pm.PackageManagerService.DOMAIN_VERIFICATION;
@@ -47,14 +46,12 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
@@ -154,45 +151,50 @@
             } break;
             case CHECK_PENDING_VERIFICATION: {
                 final int verificationId = msg.arg1;
+                final boolean streaming = msg.arg2 != 0;
                 final PackageVerificationState state = mPm.mPendingVerification.get(verificationId);
 
-                if ((state != null) && !state.isVerificationComplete()
-                        && !state.timeoutExtended()) {
-                    final VerificationParams params = state.getVerificationParams();
-                    final Uri originUri = Uri.fromFile(params.mOriginInfo.mResolvedFile);
-
-                    String errorMsg = "Verification timed out for " + originUri;
-                    Slog.i(TAG, errorMsg);
-
-                    final UserHandle user = params.getUser();
-                    if (getDefaultVerificationResponse(user)
-                            == PackageManager.VERIFICATION_ALLOW) {
-                        Slog.i(TAG, "Continuing with installation of " + originUri);
-                        state.setVerifierResponse(Binder.getCallingUid(),
-                                PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
-                        VerificationUtils.broadcastPackageVerified(verificationId, originUri,
-                                PackageManager.VERIFICATION_ALLOW, null, params.mDataLoaderType,
-                                user, mPm.mContext);
-                    } else {
-                        VerificationUtils.broadcastPackageVerified(verificationId, originUri,
-                                PackageManager.VERIFICATION_REJECT, null,
-                                params.mDataLoaderType, user, mPm.mContext);
-                        params.setReturnCode(
-                                PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
-                        state.setVerifierResponse(Binder.getCallingUid(),
-                                PackageManager.VERIFICATION_REJECT);
-                    }
-
-                    if (state.areAllVerificationsComplete()) {
-                        mPm.mPendingVerification.remove(verificationId);
-                    }
-
-                    Trace.asyncTraceEnd(
-                            TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
-
-                    params.handleVerificationFinished();
-
+                if (state == null || state.isVerificationComplete()) {
+                    // Not found or complete.
+                    break;
                 }
+                if (!streaming && state.timeoutExtended()) {
+                    // Timeout extended.
+                    break;
+                }
+
+                final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
+
+                final VerificationParams params = state.getVerificationParams();
+                final Uri originUri = Uri.fromFile(params.mOriginInfo.mResolvedFile);
+
+                String errorMsg = "Verification timed out for " + originUri;
+                Slog.i(TAG, errorMsg);
+
+                final UserHandle user = params.getUser();
+                if (response.code != PackageManager.VERIFICATION_REJECT) {
+                    Slog.i(TAG, "Continuing with installation of " + originUri);
+                    state.setVerifierResponse(response.callerUid, response.code);
+                    VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+                            PackageManager.VERIFICATION_ALLOW, null, params.mDataLoaderType,
+                            user, mPm.mContext);
+                } else {
+                    VerificationUtils.broadcastPackageVerified(verificationId, originUri,
+                            PackageManager.VERIFICATION_REJECT, null,
+                            params.mDataLoaderType, user, mPm.mContext);
+                    params.setReturnCode(
+                            PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
+                    state.setVerifierResponse(response.callerUid, response.code);
+                }
+
+                if (state.areAllVerificationsComplete()) {
+                    mPm.mPendingVerification.remove(verificationId);
+                }
+
+                Trace.asyncTraceEnd(
+                        TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
+
+                params.handleVerificationFinished();
                 break;
             }
             case CHECK_PENDING_INTEGRITY_VERIFICATION: {
@@ -241,9 +243,12 @@
                             + " It may be invalid or overridden by integrity verification");
                     break;
                 }
+                if (state.isVerificationComplete()) {
+                    Slog.w(TAG, "Verification with id " + verificationId + " already complete.");
+                    break;
+                }
 
                 final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
-
                 state.setVerifierResponse(response.callerUid, response.code);
 
                 if (state.isVerificationComplete()) {
@@ -397,21 +402,6 @@
     }
 
     /**
-     * Get the default verification agent response code.
-     *
-     * @return default verification response code
-     */
-    private int getDefaultVerificationResponse(UserHandle user) {
-        if (mPm.mUserManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS,
-                user.getIdentifier())) {
-            return PackageManager.VERIFICATION_REJECT;
-        }
-        return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
-                android.provider.Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
-                DEFAULT_VERIFICATION_RESPONSE);
-    }
-
-    /**
      * Get the default integrity verification response code.
      */
     private int getDefaultIntegrityVerificationResponse() {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 26a5bbb..f474044 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -286,6 +286,7 @@
 
         synchronized (mSessions) {
             readSessionsLocked();
+            expireSessionsLocked();
 
             reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);
 
@@ -462,34 +463,7 @@
                             Slog.e(TAG, "Could not read session", e);
                             continue;
                         }
-
-                        final long age = System.currentTimeMillis() - session.createdMillis;
-                        final long timeSinceUpdate =
-                                System.currentTimeMillis() - session.getUpdatedMillis();
-                        final boolean valid;
-                        if (session.isStaged()) {
-                            if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS
-                                    && session.isStagedAndInTerminalState()) {
-                                valid = false;
-                            } else {
-                                valid = true;
-                            }
-                        } else if (age >= MAX_AGE_MILLIS) {
-                            Slog.w(TAG, "Abandoning old session created at "
-                                        + session.createdMillis);
-                            valid = false;
-                        } else {
-                            valid = true;
-                        }
-
-                        if (valid) {
-                            mSessions.put(session.sessionId, session);
-                        } else {
-                            // Since this is early during boot we don't send
-                            // any observer events about the session, but we
-                            // keep details around for dumpsys.
-                            addHistoricalSessionLocked(session);
-                        }
+                        mSessions.put(session.sessionId, session);
                         mAllocatedSessions.put(session.sessionId, true);
                     }
                 }
@@ -509,6 +483,44 @@
     }
 
     @GuardedBy("mSessions")
+    private void expireSessionsLocked() {
+        SparseArray<PackageInstallerSession> tmp = mSessions.clone();
+        final int n = tmp.size();
+        for (int i = 0; i < n; ++i) {
+            PackageInstallerSession session = tmp.valueAt(i);
+            if (session.hasParentSessionId()) {
+                // Child sessions will be expired when handling parent sessions
+                continue;
+            }
+            final long age = System.currentTimeMillis() - session.createdMillis;
+            final long timeSinceUpdate = System.currentTimeMillis() - session.getUpdatedMillis();
+            final boolean valid;
+            if (session.isStaged()) {
+                valid = !session.isStagedAndInTerminalState()
+                        || timeSinceUpdate < MAX_TIME_SINCE_UPDATE_MILLIS;
+            } else if (age >= MAX_AGE_MILLIS) {
+                Slog.w(TAG, "Abandoning old session created at "
+                        + session.createdMillis);
+                valid = false;
+            } else {
+                valid = true;
+            }
+            if (!valid) {
+                // Remove expired sessions as well as child sessions if any
+                mSessions.remove(session.sessionId);
+                // Since this is early during boot we don't send
+                // any observer events about the session, but we
+                // keep details around for dumpsys.
+                addHistoricalSessionLocked(session);
+                for (PackageInstallerSession child : session.getChildSessions()) {
+                    mSessions.remove(child.sessionId);
+                    addHistoricalSessionLocked(child);
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mSessions")
     private void addHistoricalSessionLocked(PackageInstallerSession session) {
         CharArrayWriter writer = new CharArrayWriter();
         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
@@ -904,10 +916,26 @@
         }
     }
 
+    private boolean checkOpenSessionAccess(final PackageInstallerSession session) {
+        if (session == null) {
+            return false;
+        }
+        if (isCallingUidOwner(session)) {
+            return true;
+        }
+        // Package verifiers have access to openSession for sealed sessions.
+        if (session.isSealed() && mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        return false;
+    }
+
     private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException {
         synchronized (mSessions) {
             final PackageInstallerSession session = mSessions.get(sessionId);
-            if (session == null || !isCallingUidOwner(session)) {
+            if (!checkOpenSessionAccess(session)) {
                 throw new SecurityException("Caller has no access to session " + sessionId);
             }
             session.open();
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 55a0c96..a94985c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -706,9 +706,10 @@
                     if (mCommitted.get()) {
                         mStagingManager.abortCommittedSession(this);
                     }
-                    destroyInternal();
+                    destroy();
                     dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
-                    maybeCleanUpChildSessions();
+                    maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
+                            "Session was abandoned because the parent session is abandoned");
                 };
                 if (mStageDirInUse) {
                     // Pre-reboot verification is ongoing, not safe to clean up the session yet.
@@ -1302,22 +1303,30 @@
 
     @Override
     public String[] getNames() {
-        assertCallerIsOwnerOrRoot();
+        assertCallerIsOwnerRootOrVerifier();
         synchronized (mLock) {
-            assertPreparedAndNotCommittedOrDestroyedLocked("getNames");
-
-            return getNamesLocked();
+            assertPreparedAndNotDestroyedLocked("getNames");
+            if (!mCommitted.get()) {
+                return getNamesLocked();
+            } else {
+                return getStageDirContentsLocked();
+            }
         }
     }
 
     @GuardedBy("mLock")
+    private String[] getStageDirContentsLocked() {
+        String[] result = stageDir.list();
+        if (result == null) {
+            result = EmptyArray.STRING;
+        }
+        return result;
+    }
+
+    @GuardedBy("mLock")
     private String[] getNamesLocked() {
         if (!isDataLoaderInstallation()) {
-            String[] result = stageDir.list();
-            if (result == null) {
-                result = EmptyArray.STRING;
-            }
-            return result;
+            return getStageDirContentsLocked();
         }
 
         InstallationFile[] files = getInstallationFilesLocked();
@@ -1402,6 +1411,7 @@
     public void requestChecksums(@NonNull String name, @Checksum.TypeMask int optional,
             @Checksum.TypeMask int required, @Nullable List trustedInstallers,
             @NonNull IOnChecksumsReadyListener onChecksumsReadyListener) {
+        assertCallerIsOwnerRootOrVerifier();
         final File file = new File(stageDir, name);
         final String installerPackageName = getInstallSource().initiatingPackageName;
         try {
@@ -1669,6 +1679,23 @@
     }
 
     /**
+     * Check if the caller is the owner of this session or a verifier.
+     * Otherwise throw a {@link SecurityException}.
+     */
+    private void assertCallerIsOwnerRootOrVerifier() {
+        final int callingUid = Binder.getCallingUid();
+        if (callingUid == Process.ROOT_UID || callingUid == mInstallerUid) {
+            return;
+        }
+        if (isSealed() && mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)
+                == PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+        throw new SecurityException("Session does not belong to uid " + callingUid);
+    }
+
+    /**
      * Check if the caller is the owner of this session. Otherwise throw a
      * {@link SecurityException}.
      */
@@ -2131,6 +2158,7 @@
             destroy();
             // Dispatch message to remove session from PackageInstallerService.
             dispatchSessionFinished(error, msg, null);
+            maybeFinishChildSessions(error, msg);
         }
     }
 
@@ -3646,20 +3674,19 @@
             throw new SecurityException("Must be sealed to accept permissions");
         }
 
+        PackageInstallerSession root = hasParentSessionId()
+                ? mSessionProvider.getSession(getParentSessionId()) : this;
+
         if (accepted) {
             // Mark and kick off another install pass
             synchronized (mLock) {
                 mPermissionsManuallyAccepted = true;
             }
-
-            PackageInstallerSession root =
-                    (hasParentSessionId())
-                            ? mSessionProvider.getSession(getParentSessionId())
-                            : this;
             root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
         } else {
-            destroyInternal();
-            dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
+            root.destroy();
+            root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
+            root.maybeFinishChildSessions(INSTALL_FAILED_ABORTED, "User rejected permissions");
         }
     }
 
@@ -3710,27 +3737,12 @@
     }
 
     /**
-     * Cleans up the relevant stored files and information of all child sessions.
-     * <p>Cleaning up the stored files and session information is necessary for
-     * preventing the orphan children sessions.
-     * <ol>
-     *     <li>To call {@link #destroyInternal()} cleans up the stored files.</li>
-     *     <li>To call {@link #dispatchSessionFinished(int, String, Bundle)} to trigger the
-     *     procedure to clean up the information in PackageInstallerService.</li>
-     * </ol></p>
+     * Calls dispatchSessionFinished() on all child sessions with the given error code and
+     * error message to prevent orphaned child sessions.
      */
-    private void maybeCleanUpChildSessions() {
-        if (!isMultiPackage()) {
-            return;
-        }
-
-        final List<PackageInstallerSession> childSessions = getChildSessions();
-        final int size = childSessions.size();
-        for (int i = 0; i < size; ++i) {
-            final PackageInstallerSession session = childSessions.get(i);
-            session.destroyInternal();
-            session.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned"
-                            + " because the parent session is abandoned", null);
+    private void maybeFinishChildSessions(int returnCode, String msg) {
+        for (PackageInstallerSession child : getChildSessions()) {
+            child.dispatchSessionFinished(returnCode, msg, null);
         }
     }
 
@@ -3742,10 +3754,12 @@
                 if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
                 return;
             }
-            destroyInternal();
+            mDestroyed = true;
         }
+        destroy();
         dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
-        maybeCleanUpChildSessions();
+        maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
+                "Session was abandoned because the parent session is abandoned");
     }
 
     private void assertNotChild(String cookie) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fe5bce13..6f8703b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -57,6 +57,7 @@
 import android.app.IActivityManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
+import android.app.backup.IBackupManager;
 import android.app.role.RoleManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -582,13 +583,6 @@
 
     /** Directory where installed applications are stored */
     private final File mAppInstallDir;
-    /** Directory where installed application's 32-bit native libraries are copied. */
-    @VisibleForTesting
-    final File mAppLib32InstallDir;
-
-    static File getAppLib32InstallDir() {
-        return new File(Environment.getDataDirectory(), "app-lib");
-    }
 
     // ----------------------------------------------------------------
 
@@ -928,7 +922,7 @@
     private static final long BROADCAST_DELAY_DURING_STARTUP = 10 * 1000L; // 10 seconds (in millis)
     private static final long BROADCAST_DELAY = 1 * 1000L; // 1 second (in millis)
 
-    private static final long PRUNE_UNUSED_STATIC_SHARED_LIBRARIES_DELAY =
+    private static final long PRUNE_UNUSED_SHARED_LIBRARIES_DELAY =
             TimeUnit.MINUTES.toMillis(3); // 3 minutes
 
     // When the service constructor finished plus a delay (used for broadcast delay computation)
@@ -975,6 +969,7 @@
     private final DeletePackageHelper mDeletePackageHelper;
     private final InitAndSystemPackageHelper mInitAndSystemPackageHelper;
     private final AppDataHelper mAppDataHelper;
+    private final InstallPackageHelper mInstallPackageHelper;
     private final PreferredActivityHelper mPreferredActivityHelper;
     private final ResolveIntentHelper mResolveIntentHelper;
     private final DexOptHelper mDexOptHelper;
@@ -1259,7 +1254,12 @@
     void schedulePruneUnusedStaticSharedLibraries(boolean delay) {
         mHandler.removeMessages(PRUNE_UNUSED_STATIC_SHARED_LIBRARIES);
         mHandler.sendEmptyMessageDelayed(PRUNE_UNUSED_STATIC_SHARED_LIBRARIES,
-                delay ? PRUNE_UNUSED_STATIC_SHARED_LIBRARIES_DELAY : 0);
+                delay ? getPruneUnusedSharedLibrariesDelay() : 0);
+    }
+
+    private static long getPruneUnusedSharedLibrariesDelay() {
+        return SystemProperties.getLong("debug.pm.prune_unused_shared_libraries_delay",
+                PRUNE_UNUSED_SHARED_LIBRARIES_DELAY);
     }
 
     @Override
@@ -1284,9 +1284,6 @@
         if (!file.exists()) {
             throw new FileNotFoundException(file.getAbsolutePath());
         }
-        if (TextUtils.isEmpty(installerPackageName)) {
-            throw new FileNotFoundException(file.getAbsolutePath());
-        }
 
         final Executor executor = mInjector.getBackgroundExecutor();
         final Handler handler = mInjector.getBackgroundHandler();
@@ -1523,7 +1520,9 @@
                 new DefaultSystemWrapper(),
                 LocalServices::getService,
                 context::getSystemService,
-                (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm));
+                (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager(), pm),
+                (i, pm) -> IBackupManager.Stub.asInterface(ServiceManager.getService(
+                        Context.BACKUP_SERVICE)));
 
         if (Build.VERSION.SDK_INT <= 0) {
             Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1695,7 +1694,6 @@
         mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
         mSdkVersion = testParams.sdkVersion;
         mAppInstallDir = testParams.appInstallDir;
-        mAppLib32InstallDir = testParams.appLib32InstallDir;
         mIsEngBuild = testParams.isEngBuild;
         mIsUserDebugBuild = testParams.isUserDebugBuild;
         mIncrementalVersion = testParams.incrementalVersion;
@@ -1703,6 +1701,7 @@
 
         mBroadcastHelper = testParams.broadcastHelper;
         mAppDataHelper = testParams.appDataHelper;
+        mInstallPackageHelper = testParams.installPackageHelper;
         mRemovePackageHelper = testParams.removePackageHelper;
         mInitAndSystemPackageHelper = testParams.initAndSystemPackageHelper;
         mDeletePackageHelper = testParams.deletePackageHelper;
@@ -1835,7 +1834,6 @@
         mInstantAppRegistry = new InstantAppRegistry(this, mPermissionManager, mPmInternal);
 
         mAppInstallDir = new File(Environment.getDataDirectory(), "app");
-        mAppLib32InstallDir = getAppLib32InstallDir();
 
         mDomainVerificationConnection = new DomainVerificationConnection(this);
         mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
@@ -1843,6 +1841,7 @@
 
         mBroadcastHelper = new BroadcastHelper(mInjector);
         mAppDataHelper = new AppDataHelper(this);
+        mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
         mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
         mInitAndSystemPackageHelper = new InitAndSystemPackageHelper(this);
         mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
@@ -1975,7 +1974,7 @@
                     mIsEngBuild, mIsUserDebugBuild, mIncrementalVersion);
 
             final int[] userIds = mUserManager.getUserIds();
-            mOverlayConfig = mInitAndSystemPackageHelper.setUpSystemPackages(packageSettings,
+            mOverlayConfig = mInitAndSystemPackageHelper.initPackages(packageSettings,
                     userIds, startTime);
 
             // Resolve the storage manager.
@@ -2004,7 +2003,7 @@
                 // the rest of the commands above) because there's precious little we
                 // can do about it. A settings error is reported, though.
                 final List<String> changedAbiCodePath =
-                        ScanPackageHelper.applyAdjustedAbiToSharedUser(
+                        ScanPackageUtils.applyAdjustedAbiToSharedUser(
                                 setting, null /*scannedPackage*/,
                                 mInjector.getAbiHelper().getAdjustedAbiForSharedUser(
                                 setting.packages, null /*scannedPackage*/));
@@ -2577,7 +2576,7 @@
     }
 
     private PackageInfo generatePackageInfo(@NonNull PackageStateInternal ps,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         return mComputer.generatePackageInfo(ps, flags, userId);
     }
 
@@ -2617,13 +2616,13 @@
 
     @Override
     public PackageInfo getPackageInfo(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         return mComputer.getPackageInfo(packageName, flags, userId);
     }
 
     @Override
     public PackageInfo getPackageInfoVersioned(VersionedPackage versionedPackage,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         return mComputer.getPackageInfoInternal(versionedPackage.getPackageName(),
                 versionedPackage.getLongVersionCode(), flags, Binder.getCallingUid(), userId);
     }
@@ -2660,7 +2659,7 @@
     }
 
     private boolean filterSharedLibPackage(@Nullable PackageStateInternal ps, int uid,
-            int userId, @PackageManager.ComponentInfoFlags long flags) {
+            int userId, @PackageManager.ComponentInfoFlagsBits long flags) {
         return mComputer.filterSharedLibPackage(ps, uid, userId, flags);
     }
 
@@ -2676,17 +2675,17 @@
 
     @Override
     public int getPackageUid(@NonNull String packageName,
-        @PackageManager.PackageInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, @UserIdInt int userId) {
         return mComputer.getPackageUid(packageName, flags, userId);
     }
 
     private int getPackageUidInternal(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId, int callingUid) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
         return mComputer.getPackageUidInternal(packageName, flags, userId, callingUid);
     }
 
     @Override
-    public int[] getPackageGids(String packageName, @PackageManager.PackageInfoFlags long flags,
+    public int[] getPackageGids(String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             int userId) {
         return mComputer.getPackageGids(packageName, flags, userId);
     }
@@ -2701,14 +2700,14 @@
     }
 
     private ApplicationInfo generateApplicationInfoFromSettings(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags, int filterCallingUid, int userId) {
+            @PackageManager.ApplicationInfoFlagsBits long flags, int filterCallingUid, int userId) {
         return mComputer.generateApplicationInfoFromSettings(packageName, flags, filterCallingUid,
                 userId);
     }
 
     @Override
     public ApplicationInfo getApplicationInfo(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags, int userId) {
+            @PackageManager.ApplicationInfoFlagsBits long flags, int userId) {
         return mComputer.getApplicationInfo(packageName, flags, userId);
     }
 
@@ -2719,7 +2718,7 @@
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     private ApplicationInfo getApplicationInfoInternal(String packageName,
-            @PackageManager.ApplicationInfoFlags long flags,
+            @PackageManager.ApplicationInfoFlagsBits long flags,
             int filterCallingUid, int userId) {
         return mComputer.getApplicationInfoInternal(packageName, flags,
                 filterCallingUid, userId);
@@ -2736,13 +2735,13 @@
 
     @Override
     public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
-            final int storageFlags, final IPackageDataObserver observer) {
+            final @StorageManager.AllocateFlags int flags, final IPackageDataObserver observer) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CLEAR_APP_CACHE, null);
         mHandler.post(() -> {
             boolean success = false;
             try {
-                freeStorage(volumeUuid, freeStorageSize, storageFlags);
+                freeStorage(volumeUuid, freeStorageSize, flags);
                 success = true;
             } catch (IOException e) {
                 Slog.w(TAG, e);
@@ -2759,13 +2758,13 @@
 
     @Override
     public void freeStorage(final String volumeUuid, final long freeStorageSize,
-            final int storageFlags, final IntentSender pi) {
+            final @StorageManager.AllocateFlags int flags, final IntentSender pi) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CLEAR_APP_CACHE, TAG);
         mHandler.post(() -> {
             boolean success = false;
             try {
-                freeStorage(volumeUuid, freeStorageSize, storageFlags);
+                freeStorage(volumeUuid, freeStorageSize, flags);
                 success = true;
             } catch (IOException e) {
                 Slog.w(TAG, e);
@@ -2784,7 +2783,8 @@
      * Blocking call to clear various types of cached data across the system
      * until the requested bytes are available.
      */
-    public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws IOException {
+    public void freeStorage(String volumeUuid, long bytes,
+            @StorageManager.AllocateFlags int flags) throws IOException {
         final StorageManager storage = mInjector.getSystemService(StorageManager.class);
         final File file = storage.findPathForUuid(volumeUuid);
         if (file.getUsableSpace() >= bytes) return;
@@ -2792,8 +2792,7 @@
         if (mEnableFreeCacheV2) {
             final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,
                     volumeUuid);
-            final boolean aggressive = (storageFlags
-                    & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+            final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
 
             // 1. Pre-flight to determine if we have any chance to succeed
             // 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
@@ -2881,6 +2880,21 @@
         throw new IOException("Failed to free " + bytes + " on storage device at " + file);
     }
 
+    private PackageSetting getLibraryPackage(SharedLibraryInfo libInfo) {
+        final VersionedPackage declaringPackage = libInfo.getDeclaringPackage();
+        if (libInfo.isStatic()) {
+            // Resolve the package name - we use synthetic package names internally
+            final String internalPackageName = resolveInternalPackageNameLPr(
+                    declaringPackage.getPackageName(),
+                    declaringPackage.getLongVersionCode());
+            return mSettings.getPackageLPr(internalPackageName);
+        }
+        if (libInfo.isSdk()) {
+            return mSettings.getPackageLPr(declaringPackage.getPackageName());
+        }
+        return null;
+    }
+
     boolean pruneUnusedStaticSharedLibraries(long neededSpace, long maxCachePeriod)
             throws IOException {
         final StorageManager storage = mInjector.getSystemService(StorageManager.class);
@@ -2889,6 +2903,9 @@
         List<VersionedPackage> packagesToDelete = null;
         final long now = System.currentTimeMillis();
 
+        // Important: We skip shared libs used for some user since
+        // in such a case we need to keep the APK on the device. The check for
+        // a lib being used for any user is performed by the uninstall call.
         synchronized (mLock) {
             final int libCount = mSharedLibraries.size();
             for (int i = 0; i < libCount; i++) {
@@ -2900,22 +2917,13 @@
                 final int versionCount = versionedLib.size();
                 for (int j = 0; j < versionCount; j++) {
                     SharedLibraryInfo libInfo = versionedLib.valueAt(j);
-                    // Skip packages that are not static shared libs.
-                    if (!libInfo.isStatic()) {
-                        break;
+                    final PackageSetting ps = getLibraryPackage(libInfo);
+                    if (ps == null) {
+                        continue;
                     }
-                    // Important: We skip static shared libs used for some user since
-                    // in such a case we need to keep the APK on the device. The check for
-                    // a lib being used for any user is performed by the uninstall call.
-                    final VersionedPackage declaringPackage = libInfo.getDeclaringPackage();
-                    // Resolve the package name - we use synthetic package names internally
-                    final String internalPackageName = resolveInternalPackageNameLPr(
-                            declaringPackage.getPackageName(),
-                            declaringPackage.getLongVersionCode());
-                    final PackageSetting ps = mSettings.getPackageLPr(internalPackageName);
-                    // Skip unused static shared libs cached less than the min period
-                    // to prevent pruning a lib needed by a subsequently installed package.
-                    if (ps == null || now - ps.getLastUpdateTime() < maxCachePeriod) {
+                    // Skip unused libs cached less than the min period to prevent pruning a lib
+                    // needed by a subsequently installed package.
+                    if (now - ps.getLastUpdateTime() < maxCachePeriod) {
                         continue;
                     }
 
@@ -2926,8 +2934,8 @@
                     if (packagesToDelete == null) {
                         packagesToDelete = new ArrayList<>();
                     }
-                    packagesToDelete.add(new VersionedPackage(internalPackageName,
-                            declaringPackage.getLongVersionCode()));
+                    packagesToDelete.add(new VersionedPackage(ps.getPkg().getPackageName(),
+                            libInfo.getDeclaringPackage().getLongVersionCode()));
                 }
             }
         }
@@ -2997,7 +3005,7 @@
 
     @Override
     public ActivityInfo getActivityInfo(ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
         return mComputer.getActivityInfo(component, flags, userId);
     }
 
@@ -3008,7 +3016,7 @@
      * trusted and will be used as-is; unlike userId which will be validated by this method.
      */
     private ActivityInfo getActivityInfoInternal(ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, int filterCallingUid, int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, int filterCallingUid, int userId) {
         return mComputer.getActivityInfoInternal(component, flags,
                 filterCallingUid, userId);
     }
@@ -3022,42 +3030,42 @@
 
     @Override
     public ActivityInfo getReceiverInfo(ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, int userId) {
         return mComputer.getReceiverInfo(component, flags, userId);
     }
 
     @Override
     public ParceledListSlice<SharedLibraryInfo> getSharedLibraries(String packageName,
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         return mComputer.getSharedLibraries(packageName, flags, userId);
     }
 
     @Nullable
     @Override
     public ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries(
-            @NonNull String packageName, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String packageName, @PackageManager.PackageInfoFlagsBits long flags,
             @NonNull int userId) {
         return mComputer.getDeclaredSharedLibraries(packageName, flags, userId);
     }
 
     @Nullable
     List<VersionedPackage> getPackagesUsingSharedLibrary(
-            SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlags long flags, int callingUid,
-            int userId) {
+            SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags,
+            int callingUid, int userId) {
         return mComputer.getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId);
     }
 
     @Nullable
     @Override
     public ServiceInfo getServiceInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         return mComputer.getServiceInfo(component, flags, userId);
     }
 
     @Nullable
     @Override
     public ProviderInfo getProviderInfo(@NonNull ComponentName component,
-            @PackageManager.ComponentInfoFlags long flags, @UserIdInt int userId) {
+            @PackageManager.ComponentInfoFlagsBits long flags, @UserIdInt int userId) {
         return mComputer.getProviderInfo(component, flags, userId);
     }
 
@@ -3346,7 +3354,7 @@
 
     @Override
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return mResolveIntentHelper.resolveIntentInternal(intent, resolvedType, flags,
                 0 /*privateResolveFlags*/, userId, false, Binder.getCallingUid());
     }
@@ -3391,7 +3399,7 @@
      */
     @GuardedBy("mLock")
     boolean isImplicitImageCaptureIntentAndNotSetByDpcLocked(Intent intent, int userId,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags) {
         return mComputer.isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
                 resolvedType, flags);
     }
@@ -3399,7 +3407,7 @@
     @GuardedBy("mLock")
     ResolveInfo findPersistentPreferredActivityLP(Intent intent,
             String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean debug,
+            @PackageManager.ResolveInfoFlagsBits long flags, List<ResolveInfo> query, boolean debug,
             int userId) {
         return mComputer.findPersistentPreferredActivityLP(intent,
                 resolvedType, flags, query, debug, userId);
@@ -3413,7 +3421,7 @@
     }
 
     FindPreferredActivityBodyResult findPreferredActivityInternal(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean always,
             boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
         return mComputer.findPreferredActivityInternal(
@@ -3443,7 +3451,7 @@
 
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivities(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
 
@@ -3463,13 +3471,13 @@
     }
 
     @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return mComputer.queryIntentActivitiesInternal(intent,
                 resolvedType, flags, userId);
     }
 
     @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             @PrivateResolveFlags long privateResolveFlags, int filterCallingUid, int userId,
             boolean resolveForStart, boolean allowDynamicSplits) {
         return mComputer.queryIntentActivitiesInternal(intent,
@@ -3478,7 +3486,7 @@
     }
 
     private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int sourceUserId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int sourceUserId,
             int parentUserId) {
         return mComputer.getCrossProfileDomainPreferredLpr(intent,
                 resolvedType, flags, sourceUserId, parentUserId);
@@ -3506,21 +3514,21 @@
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
             Intent[] specifics, String[] specificTypes, Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return new ParceledListSlice<>(mResolveIntentHelper.queryIntentActivityOptionsInternal(
                 caller, specifics, specificTypes, intent, resolvedType, flags, userId));
     }
 
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return new ParceledListSlice<>(mResolveIntentHelper.queryIntentReceiversInternal(intent,
                 resolvedType, flags, userId, Binder.getCallingUid()));
     }
 
     @Override
     public ResolveInfo resolveService(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         final int callingUid = Binder.getCallingUid();
         return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
                 callingUid);
@@ -3528,14 +3536,14 @@
 
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentServices(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         final int callingUid = Binder.getCallingUid();
         return new ParceledListSlice<>(queryIntentServicesInternal(
                 intent, resolvedType, flags, userId, callingUid, false /*includeInstantApps*/));
     }
 
     @NonNull List<ResolveInfo> queryIntentServicesInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
             int callingUid, boolean includeInstantApps) {
         return mComputer.queryIntentServicesInternal(intent,
                 resolvedType, flags, userId, callingUid,
@@ -3544,27 +3552,27 @@
 
     @Override
     public @NonNull ParceledListSlice<ResolveInfo> queryIntentContentProviders(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return new ParceledListSlice<>(mResolveIntentHelper.queryIntentContentProvidersInternal(
                 intent, resolvedType, flags, userId));
     }
 
     @Override
     public ParceledListSlice<PackageInfo> getInstalledPackages(
-            @PackageManager.PackageInfoFlags long flags, int userId) {
+            @PackageManager.PackageInfoFlagsBits long flags, int userId) {
         return mComputer.getInstalledPackages(flags, userId);
     }
 
     @Override
     public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(
-            @NonNull String[] permissions, @PackageManager.PackageInfoFlags long flags,
+            @NonNull String[] permissions, @PackageManager.PackageInfoFlagsBits long flags,
             @UserIdInt int userId) {
         return mComputer.getPackagesHoldingPermissions(permissions, flags, userId);
     }
 
     @Override
     public ParceledListSlice<ApplicationInfo> getInstalledApplications(
-            @PackageManager.ApplicationInfoFlags long flags, int userId) {
+            @PackageManager.ApplicationInfoFlagsBits long flags, int userId) {
         final int callingUid = Binder.getCallingUid();
         return new ParceledListSlice<>(
                 mComputer.getInstalledApplications(flags, userId, callingUid));
@@ -3669,7 +3677,7 @@
 
     @Override
     public ProviderInfo resolveContentProvider(String name,
-            @PackageManager.ResolveInfoFlags long flags, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         return mComputer.resolveContentProvider(name, flags, userId, Binder.getCallingUid());
     }
 
@@ -3681,7 +3689,8 @@
     @NonNull
     @Override
     public ParceledListSlice<ProviderInfo> queryContentProviders(@Nullable  String processName,
-            int uid, @PackageManager.ComponentInfoFlags long flags, @Nullable String metaDataKey) {
+            int uid, @PackageManager.ComponentInfoFlagsBits long flags,
+            @Nullable String metaDataKey) {
         return mComputer.queryContentProviders(processName, uid, flags, metaDataKey);
     }
 
@@ -4686,35 +4695,10 @@
     @Override
     public int installExistingPackageAsUser(String packageName, int userId, int installFlags,
             int installReason, List<String> whiteListedPermissions) {
-        final InstallPackageHelper installPackageHelper = new InstallPackageHelper(
-                this, mAppDataHelper);
-        return installPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
+        return mInstallPackageHelper.installExistingPackageAsUser(packageName, userId, installFlags,
                 installReason, whiteListedPermissions, null);
     }
 
-    static void setInstantAppForUser(PackageManagerServiceInjector injector,
-            PackageSetting pkgSetting, int userId, boolean instantApp, boolean fullApp) {
-        // no state specified; do nothing
-        if (!instantApp && !fullApp) {
-            return;
-        }
-        if (userId != UserHandle.USER_ALL) {
-            if (instantApp && !pkgSetting.getInstantApp(userId)) {
-                pkgSetting.setInstantApp(true /*instantApp*/, userId);
-            } else if (fullApp && pkgSetting.getInstantApp(userId)) {
-                pkgSetting.setInstantApp(false /*instantApp*/, userId);
-            }
-        } else {
-            for (int currentUserId : injector.getUserManagerInternal().getUserIds()) {
-                if (instantApp && !pkgSetting.getInstantApp(currentUserId)) {
-                    pkgSetting.setInstantApp(true /*instantApp*/, currentUserId);
-                } else if (fullApp && pkgSetting.getInstantApp(currentUserId)) {
-                    pkgSetting.setInstantApp(false /*instantApp*/, currentUserId);
-                }
-            }
-        }
-    }
-
     boolean isUserRestricted(int userId, String restrictionKey) {
         Bundle restrictions = mUserManager.getUserRestrictions(userId);
         if (restrictions.getBoolean(restrictionKey, false)) {
@@ -5208,10 +5192,11 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
                 "Only package verification agents can verify applications");
+        final int callingUid = Binder.getCallingUid();
 
         final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
         final PackageVerificationResponse response = new PackageVerificationResponse(
-                verificationCode, Binder.getCallingUid());
+                verificationCode, callingUid);
         msg.arg1 = id;
         msg.obj = response;
         mHandler.sendMessage(msg);
@@ -5223,11 +5208,12 @@
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
                 "Only package verification agents can extend verification timeouts");
+        final int callingUid = Binder.getCallingUid();
 
         mHandler.post(() -> {
             final PackageVerificationState state = mPendingVerification.get(id);
             final PackageVerificationResponse response = new PackageVerificationResponse(
-                    verificationCodeAtTimeout, Binder.getCallingUid());
+                    verificationCodeAtTimeout, callingUid);
 
             long delay = millisecondsToDelay;
             if (delay > PackageManager.MAXIMUM_VERIFICATION_TIMEOUT) {
@@ -6719,8 +6705,7 @@
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                     || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
-                if (!new InstallPackageHelper(this).enableCompressedPackage(deletedPkg,
-                        pkgSetting)) {
+                if (!mInstallPackageHelper.enableCompressedPackage(deletedPkg, pkgSetting)) {
                     Slog.w(TAG, "Failed setApplicationEnabledSetting: failed to enable "
                             + "commpressed package " + setting.getPackageName());
                     updateAllowed[i] = false;
@@ -7561,7 +7546,7 @@
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
         public List<ApplicationInfo> getInstalledApplications(
-                @PackageManager.ApplicationInfoFlags long flags, int userId, int callingUid) {
+                @PackageManager.ApplicationInfoFlagsBits long flags, int userId, int callingUid) {
             return PackageManagerService.this.mComputer.getInstalledApplications(flags, userId,
                     callingUid);
         }
@@ -7756,7 +7741,7 @@
 
         @Override
         public PackageInfo getPackageInfo(
-                String packageName, @PackageManager.PackageInfoFlags long flags,
+                String packageName, @PackageManager.PackageInfoFlagsBits long flags,
                 int filterCallingUid, int userId) {
             return PackageManagerService.this.mComputer
                     .getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
@@ -7885,15 +7870,15 @@
         }
 
         @Override
-        public int getPackageUid(String packageName, @PackageManager.PackageInfoFlags long flags,
-                int userId) {
+        public int getPackageUid(String packageName,
+                @PackageManager.PackageInfoFlagsBits long flags, int userId) {
             return PackageManagerService.this
                     .getPackageUidInternal(packageName, flags, userId, Process.SYSTEM_UID);
         }
 
         @Override
         public ApplicationInfo getApplicationInfo(
-                String packageName, @PackageManager.ApplicationInfoFlags long flags,
+                String packageName, @PackageManager.ApplicationInfoFlagsBits long flags,
                 int filterCallingUid, int userId) {
             return PackageManagerService.this
                     .getApplicationInfoInternal(packageName, flags, filterCallingUid, userId);
@@ -7901,7 +7886,7 @@
 
         @Override
         public ActivityInfo getActivityInfo(
-                ComponentName component, @PackageManager.ComponentInfoFlags long flags,
+                ComponentName component, @PackageManager.ComponentInfoFlagsBits long flags,
                 int filterCallingUid, int userId) {
             return PackageManagerService.this
                     .getActivityInfoInternal(component, flags, filterCallingUid, userId);
@@ -7909,7 +7894,7 @@
 
         @Override
         public List<ResolveInfo> queryIntentActivities(
-                Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+                Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
                 int filterCallingUid, int userId) {
             return PackageManagerService.this
                     .queryIntentActivitiesInternal(intent, resolvedType, flags, 0, filterCallingUid,
@@ -7918,7 +7903,7 @@
 
         @Override
         public List<ResolveInfo> queryIntentReceivers(Intent intent,
-                String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+                String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
                 int filterCallingUid, int userId) {
             return PackageManagerService.this.mResolveIntentHelper.queryIntentReceiversInternal(
                     intent, resolvedType, flags, userId, filterCallingUid);
@@ -7926,7 +7911,7 @@
 
         @Override
         public List<ResolveInfo> queryIntentServices(
-                Intent intent, @PackageManager.ResolveInfoFlags long flags, int callingUid,
+                Intent intent, @PackageManager.ResolveInfoFlagsBits long flags, int callingUid,
                 int userId) {
             final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
             return PackageManagerService.this
@@ -8195,7 +8180,7 @@
 
         @Override
         public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-                @PackageManager.ResolveInfoFlags long flags,
+                @PackageManager.ResolveInfoFlagsBits long flags,
                 @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
                 boolean resolveForStart, int filterCallingUid) {
             return mResolveIntentHelper.resolveIntentInternal(
@@ -8205,14 +8190,14 @@
 
         @Override
         public ResolveInfo resolveService(Intent intent, String resolvedType,
-                @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) {
+                @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid) {
             return mResolveIntentHelper.resolveServiceInternal(intent, resolvedType, flags, userId,
                     callingUid);
         }
 
         @Override
         public ProviderInfo resolveContentProvider(String name,
-                @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) {
+                @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid) {
             return PackageManagerService.this.mComputer
                     .resolveContentProvider(name, flags, userId,callingUid);
         }
@@ -8297,9 +8282,9 @@
         }
 
         @Override
-        public void freeStorage(String volumeUuid, long bytes, int storageFlags)
-                throws IOException {
-            PackageManagerService.this.freeStorage(volumeUuid, bytes, storageFlags);
+        public void freeStorage(String volumeUuid, long bytes,
+                @StorageManager.AllocateFlags int flags) throws IOException {
+            PackageManagerService.this.freeStorage(volumeUuid, bytes, flags);
         }
 
         @Override
@@ -8681,7 +8666,8 @@
         }
 
         @Override
-        public void reconcileAppsData(int userId, int flags, boolean migrateAppsData) {
+        public void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
+                boolean migrateAppsData) {
             PackageManagerService.this.mAppDataHelper.reconcileAppsData(userId, flags,
                     migrateAppsData);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 97a09ff..d14cc1f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import android.app.ActivityManagerInternal;
+import android.app.backup.IBackupManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
@@ -135,6 +136,7 @@
             mDomainVerificationManagerInternalProducer;
     private final Singleton<Handler> mHandlerProducer;
     private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
+    private final Singleton<IBackupManager> mIBackupManager;
 
     PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
             Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -170,7 +172,8 @@
             SystemWrapper systemWrapper,
             ServiceProducer getLocalServiceProducer,
             ServiceProducer getSystemServiceProducer,
-            Producer<BackgroundDexOptService> backgroundDexOptService) {
+            Producer<BackgroundDexOptService> backgroundDexOptService,
+            Producer<IBackupManager> iBackupManager) {
         mContext = context;
         mLock = lock;
         mInstaller = installer;
@@ -220,6 +223,7 @@
                         domainVerificationManagerInternalProducer);
         mHandlerProducer = new Singleton<>(handlerProducer);
         mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
+        mIBackupManager = new Singleton<>(iBackupManager);
     }
 
     /**
@@ -384,6 +388,10 @@
         return mBackgroundDexOptService.get(this, mPackageManager);
     }
 
+    public IBackupManager getIBackupManager() {
+        return mIBackupManager.get(this, mPackageManager);
+    }
+
     /** Provides an abstraction to static access to system state. */
     public interface SystemWrapper {
         void disablePackageCaches();
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 9327c5f..a1acc38 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -104,6 +104,7 @@
     public final String incrementalVersion = Build.VERSION.INCREMENTAL;
     public BroadcastHelper broadcastHelper;
     public AppDataHelper appDataHelper;
+    public InstallPackageHelper installPackageHelper;
     public RemovePackageHelper removePackageHelper;
     public InitAndSystemPackageHelper initAndSystemPackageHelper;
     public DeletePackageHelper deletePackageHelper;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 3b643b5..898f673 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -18,9 +18,11 @@
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
 
+import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
 import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
 import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
@@ -34,7 +36,7 @@
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -60,6 +62,7 @@
 import android.os.Process;
 import android.os.SystemProperties;
 import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
 import android.os.incremental.V4Signature;
 import android.os.incremental.V4Signature.HashingInfo;
 import android.os.storage.DiskInfo;
@@ -84,6 +87,7 @@
 import com.android.internal.util.HexDump;
 import com.android.server.EventLogTags;
 import com.android.server.IntentResolver;
+import com.android.server.Watchdog;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -141,7 +145,7 @@
      * allow 3P apps to trigger internal-only functionality.
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S)
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
 
     /**
@@ -649,6 +653,58 @@
     }
 
     /**
+     * Extract native libraries to a target path
+     */
+    public static int extractNativeBinaries(File dstCodePath, String packageName) {
+        final File libraryRoot = new File(dstCodePath, LIB_DIR_NAME);
+        NativeLibraryHelper.Handle handle = null;
+        try {
+            handle = NativeLibraryHelper.Handle.create(dstCodePath);
+            return NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
+                    null /*abiOverride*/, false /*isIncremental*/);
+        } catch (IOException e) {
+            logCriticalInfo(Log.ERROR, "Failed to extract native libraries"
+                    + "; pkg: " + packageName);
+            return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        } finally {
+            IoUtils.closeQuietly(handle);
+        }
+    }
+
+    /**
+     * Remove native libraries of a given package
+     */
+    public static void removeNativeBinariesLI(PackageSetting ps) {
+        if (ps != null) {
+            NativeLibraryHelper.removeNativeBinariesLI(ps.getLegacyNativeLibraryPath());
+        }
+    }
+
+    /**
+     * Wait for native library extraction to be done in IncrementalService
+     */
+    public static void waitForNativeBinariesExtractionForIncremental(
+            ArraySet<IncrementalStorage> incrementalStorages) {
+        if (incrementalStorages.isEmpty()) {
+            return;
+        }
+        try {
+            // Native library extraction may take very long time: each page could potentially
+            // wait for either 10s or 100ms (adb vs non-adb data loader), and that easily adds
+            // up to a full watchdog timeout of 1 min, killing the system after that. It doesn't
+            // make much sense as blocking here doesn't lock up the framework, but only blocks
+            // the installation session and the following ones.
+            Watchdog.getInstance().pauseWatchingCurrentThread("native_lib_extract");
+            for (int i = 0; i < incrementalStorages.size(); ++i) {
+                IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i);
+                storage.waitForNativeBinariesExtraction();
+            }
+        } finally {
+            Watchdog.getInstance().resumeWatchingCurrentThread("native_lib_extract");
+        }
+    }
+
+    /**
      * Decompress files stored in codePath to dstCodePath for a certain package.
      */
     public static int decompressFiles(String codePath, File dstCodePath, String packageName) {
@@ -1079,7 +1135,7 @@
     public static boolean hasAnyDomainApproval(
             @NonNull DomainVerificationManagerInternal manager,
             @NonNull PackageStateInternal pkgSetting, @NonNull Intent intent,
-            @PackageManager.ResolveInfoFlags long resolveInfoFlags, @UserIdInt int userId) {
+            @PackageManager.ResolveInfoFlagsBits long resolveInfoFlags, @UserIdInt int userId) {
         return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId)
                 > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
     }
@@ -1280,4 +1336,39 @@
 
         return cacheDir;
     }
+
+    /**
+     * Check and throw if the given before/after packages would be considered a
+     * downgrade.
+     */
+    public static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
+            throws PackageManagerException {
+        if (after.getLongVersionCode() < before.getLongVersionCode()) {
+            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                    "Update version code " + after.versionCode + " is older than current "
+                            + before.getLongVersionCode());
+        } else if (after.getLongVersionCode() == before.getLongVersionCode()) {
+            if (after.baseRevisionCode < before.getBaseRevisionCode()) {
+                throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                        "Update base revision code " + after.baseRevisionCode
+                                + " is older than current " + before.getBaseRevisionCode());
+            }
+
+            if (!ArrayUtils.isEmpty(after.splitNames)) {
+                for (int i = 0; i < after.splitNames.length; i++) {
+                    final String splitName = after.splitNames[i];
+                    final int j = ArrayUtils.indexOf(before.getSplitNames(), splitName);
+                    if (j != -1) {
+                        if (after.splitRevisionCodes[i] < before.getSplitRevisionCodes()[j]) {
+                            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                                    "Update split " + splitName + " revision code "
+                                            + after.splitRevisionCodes[i]
+                                            + " is older than current "
+                                            + before.getSplitRevisionCodes()[j]);
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index a60d2c8..48dc3cb 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -49,6 +49,7 @@
     boolean mDataRemoved;
     boolean mRemovedForAllUsers;
     boolean mIsStaticSharedLib;
+    boolean mAppIdChanging = false;
     // a two dimensional array mapping userId to the set of appIds that can receive notice
     // of package changes
     SparseArray<int[]> mBroadcastAllowList;
@@ -64,33 +65,43 @@
         sendPackageRemovedBroadcastInternal(killApp, removedBySystem);
     }
 
-    void sendSystemPackageUpdatedBroadcasts() {
+    void sendSystemPackageUpdatedBroadcasts(int newAppId) {
         if (mIsRemovedPackageSystemUpdate) {
-            sendSystemPackageUpdatedBroadcastsInternal();
+            sendSystemPackageUpdatedBroadcastsInternal(newAppId);
         }
     }
 
-    private void sendSystemPackageUpdatedBroadcastsInternal() {
+    private void sendSystemPackageUpdatedBroadcastsInternal(int newAppId) {
         Bundle extras = new Bundle(2);
-        extras.putInt(Intent.EXTRA_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid);
-        extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        extras.putInt(Intent.EXTRA_UID, newAppId);
+        // When appId changes, do not set the replacing extra
+        if (mAppIdChanging) {
+            extras.putBoolean(Intent.EXTRA_UID_CHANGING, true);
+            extras.putInt(Intent.EXTRA_PREVIOUS_UID, mRemovedAppId >= 0 ? mRemovedAppId : mUid);
+        } else {
+            extras.putBoolean(Intent.EXTRA_REPLACING, true);
+        }
         mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, mRemovedPackage, extras,
                 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage,
-                extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
-        mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
-                mRemovedPackage, null, null, null, null /* broadcastAllowList */,
-                getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());
         if (mInstallerPackageName != null) {
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                     mRemovedPackage, extras, 0 /*flags*/,
                     mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
                     null);
-            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
-                    mRemovedPackage, extras, 0 /*flags*/,
-                    mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
-                    null);
         }
+        if (!mAppIdChanging) {
+            mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, mRemovedPackage,
+                    extras, 0, null /*targetPackage*/, null, null, null, mBroadcastAllowList, null);
+            if (mInstallerPackageName != null) {
+                mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                        mRemovedPackage, extras, 0 /*flags*/,
+                        mInstallerPackageName, null, null, null, null /* broadcastAllowList */,
+                        null);
+            }
+        }
+        mPackageSender.sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, 0,
+                mRemovedPackage, null, null, null, null /* broadcastAllowList */,
+                getTemporaryAppAllowlistBroadcastOptions(REASON_PACKAGE_REPLACED).toBundle());
     }
 
     private static @NonNull BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@@ -115,15 +126,20 @@
         if (mIsStaticSharedLib) {
             return;
         }
-        Bundle extras = new Bundle(2);
+        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_DONT_KILL_APP, !killApp);
         extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
-        if (mIsUpdate || mIsRemovedPackageSystemUpdate) {
+
+        // When appId changes, do not set the replacing extra
+        if (mAppIdChanging) {
+            extras.putBoolean(Intent.EXTRA_UID_CHANGING, true);
+        } else if (mIsUpdate || mIsRemovedPackageSystemUpdate) {
             extras.putBoolean(Intent.EXTRA_REPLACING, true);
         }
+
         extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
         if (mRemovedPackage != null) {
             mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
@@ -146,9 +162,9 @@
             }
         }
         if (mRemovedAppId >= 0) {
-            // If a system app's updates are uninstalled the UID is not actually removed. Some
-            // services need to know the package name affected.
-            if (extras.getBoolean(Intent.EXTRA_REPLACING, false)) {
+            // If the package is not actually removed, some services need to know the
+            // package name affected.
+            if (mAppIdChanging || mIsUpdate || mIsRemovedPackageSystemUpdate) {
                 extras.putString(Intent.EXTRA_PACKAGE_NAME, mRemovedPackage);
             }
 
diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
index 8c91b16..802f701 100644
--- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java
+++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java
@@ -75,8 +75,8 @@
     }
 
     private ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags, List<ResolveInfo> query, boolean always,
-            boolean removeMatches, boolean debug, int userId) {
+            @PackageManager.ResolveInfoFlagsBits long flags, List<ResolveInfo> query,
+            boolean always, boolean removeMatches, boolean debug, int userId) {
         return findPreferredActivityNotLocked(
                 intent, resolvedType, flags, query, always, removeMatches, debug, userId,
                 UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
@@ -85,7 +85,7 @@
     // TODO: handle preferred activities missing while user has amnesia
     /** <b>must not hold {@link PackageManagerService.mLock}</b> */
     public ResolveInfo findPreferredActivityNotLocked(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             List<ResolveInfo> query, boolean always, boolean removeMatches, boolean debug,
             int userId, boolean queryMayBeFiltered) {
         if (Thread.holdsLock(mPm.mLock)) {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
new file mode 100644
index 0000000..5a25004
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -0,0 +1,292 @@
+/*
+ * 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.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
+
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.utils.WatchedLongSparseArray;
+
+import java.util.List;
+import java.util.Map;
+
+final class ReconcilePackageUtils {
+    public static Map<String, ReconciledPackage> reconcilePackages(
+            final ReconcileRequest request, KeySetManagerService ksms,
+            PackageManagerServiceInjector injector)
+            throws ReconcileFailure {
+        final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
+
+        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
+
+        // make a copy of the existing set of packages so we can combine them with incoming packages
+        final ArrayMap<String, AndroidPackage> combinedPackages =
+                new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
+
+        combinedPackages.putAll(request.mAllPackages);
+
+        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
+                new ArrayMap<>();
+
+        for (String installPackageName : scannedPackages.keySet()) {
+            final ScanResult scanResult = scannedPackages.get(installPackageName);
+
+            // add / replace existing with incoming packages
+            combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
+                    scanResult.mRequest.mParsedPackage);
+
+            // in the first pass, we'll build up the set of incoming shared libraries
+            final List<SharedLibraryInfo> allowedSharedLibInfos =
+                    SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
+                            request.mSharedLibrarySource);
+            if (allowedSharedLibInfos != null) {
+                for (SharedLibraryInfo info : allowedSharedLibInfos) {
+                    if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
+                            incomingSharedLibraries, info)) {
+                        throw new ReconcileFailure("Shared Library " + info.getName()
+                                + " is being installed twice in this set!");
+                    }
+                }
+            }
+
+            // the following may be null if we're just reconciling on boot (and not during install)
+            final InstallArgs installArgs = request.mInstallArgs.get(installPackageName);
+            final PackageInstalledInfo res = request.mInstallResults.get(installPackageName);
+            final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
+            final boolean isInstall = installArgs != null;
+            if (isInstall && (res == null || prepareResult == null)) {
+                throw new ReconcileFailure("Reconcile arguments are not balanced for "
+                        + installPackageName + "!");
+            }
+
+            final DeletePackageAction deletePackageAction;
+            // we only want to try to delete for non system apps
+            if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
+                final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
+                final int deleteFlags = PackageManager.DELETE_KEEP_DATA
+                        | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
+                deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(res.mRemovedInfo,
+                        prepareResult.mOriginalPs, prepareResult.mDisabledPs,
+                        deleteFlags, null /* all users */);
+                if (deletePackageAction == null) {
+                    throw new ReconcileFailure(
+                            PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+                            "May not delete " + installPackageName + " to replace");
+                }
+            } else {
+                deletePackageAction = null;
+            }
+
+            final int scanFlags = scanResult.mRequest.mScanFlags;
+            final int parseFlags = scanResult.mRequest.mParseFlags;
+            final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
+
+            final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
+            final PackageSetting lastStaticSharedLibSetting =
+                    request.mLastStaticSharedLibSettings.get(installPackageName);
+            final PackageSetting signatureCheckPs =
+                    (prepareResult != null && lastStaticSharedLibSetting != null)
+                            ? lastStaticSharedLibSetting
+                            : scanResult.mPkgSetting;
+            boolean removeAppKeySetData = false;
+            boolean sharedUserSignaturesChanged = false;
+            SigningDetails signingDetails = null;
+            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                } else {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                "Package " + parsedPackage.getPackageName()
+                                        + " upgrade keys do not match the previously installed"
+                                        + " version");
+                    } else {
+                        String msg = "System package " + parsedPackage.getPackageName()
+                                + " signature changed; retaining data.";
+                        PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    }
+                }
+                signingDetails = parsedPackage.getSigningDetails();
+            } else {
+                try {
+                    final Settings.VersionInfo versionInfo =
+                            request.mVersionInfos.get(installPackageName);
+                    final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
+                    final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
+                    final boolean isRollback = installArgs != null
+                            && installArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
+                    final boolean compatMatch = verifySignatures(signatureCheckPs,
+                            disabledPkgSetting, parsedPackage.getSigningDetails(), compareCompat,
+                            compareRecover, isRollback);
+                    // The new KeySets will be re-added later in the scanning process.
+                    if (compatMatch) {
+                        removeAppKeySetData = true;
+                    }
+                    // We just determined the app is signed correctly, so bring
+                    // over the latest parsed certs.
+                    signingDetails = parsedPackage.getSigningDetails();
+
+                    // if this is is a sharedUser, check to see if the new package is signed by a
+                    // newer
+                    // signing certificate than the existing one, and if so, copy over the new
+                    // details
+                    if (signatureCheckPs.getSharedUser() != null) {
+                        // Attempt to merge the existing lineage for the shared SigningDetails with
+                        // the lineage of the new package; if the shared SigningDetails are not
+                        // returned this indicates the new package added new signers to the lineage
+                        // and/or changed the capabilities of existing signers in the lineage.
+                        SigningDetails sharedSigningDetails =
+                                signatureCheckPs.getSharedUser().signatures.mSigningDetails;
+                        SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith(
+                                signingDetails);
+                        if (mergedDetails != sharedSigningDetails) {
+                            signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+                                    mergedDetails;
+                        }
+                        if (signatureCheckPs.getSharedUser().signaturesChanged == null) {
+                            signatureCheckPs.getSharedUser().signaturesChanged = Boolean.FALSE;
+                        }
+                    }
+                } catch (PackageManagerException e) {
+                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                        throw new ReconcileFailure(e);
+                    }
+                    signingDetails = parsedPackage.getSigningDetails();
+
+                    // If the system app is part of a shared user we allow that shared user to
+                    // change
+                    // signatures as well as part of an OTA. We still need to verify that the
+                    // signatures
+                    // are consistent within the shared user for a given boot, so only allow
+                    // updating
+                    // the signatures on the first package scanned for the shared user (i.e. if the
+                    // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
+                    if (signatureCheckPs.getSharedUser() != null) {
+                        final Signature[] sharedUserSignatures = signatureCheckPs.getSharedUser()
+                                .signatures.mSigningDetails.getSignatures();
+                        if (signatureCheckPs.getSharedUser().signaturesChanged != null
+                                && compareSignatures(sharedUserSignatures,
+                                parsedPackage.getSigningDetails().getSignatures())
+                                != PackageManager.SIGNATURE_MATCH) {
+                            if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
+                                // Mismatched signatures is an error and silently skipping system
+                                // packages will likely break the device in unforeseen ways.
+                                // However, we allow the device to boot anyway because, prior to Q,
+                                // vendors were not expecting the platform to crash in this
+                                // situation.
+                                // This WILL be a hard failure on any new API levels after Q.
+                                throw new ReconcileFailure(
+                                        INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                        "Signature mismatch for shared user: "
+                                                + scanResult.mPkgSetting.getSharedUser());
+                            } else {
+                                // Treat mismatched signatures on system packages using a shared
+                                // UID as
+                                // fatal for the system overall, rather than just failing to install
+                                // whichever package happened to be scanned later.
+                                throw new IllegalStateException(
+                                        "Signature mismatch on system package "
+                                                + parsedPackage.getPackageName()
+                                                + " for shared user "
+                                                + scanResult.mPkgSetting.getSharedUser());
+                            }
+                        }
+
+                        sharedUserSignaturesChanged = true;
+                        signatureCheckPs.getSharedUser().signatures.mSigningDetails =
+                                parsedPackage.getSigningDetails();
+                        signatureCheckPs.getSharedUser().signaturesChanged = Boolean.TRUE;
+                    }
+                    // File a report about this.
+                    String msg = "System package " + parsedPackage.getPackageName()
+                            + " signature changed; retaining data.";
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                } catch (IllegalArgumentException e) {
+                    // should never happen: certs matched when checking, but not when comparing
+                    // old to new for sharedUser
+                    throw new RuntimeException(
+                            "Signing certificates comparison made on incomparable signing details"
+                                    + " but somehow passed verifySignatures!", e);
+                }
+            }
+
+            result.put(installPackageName,
+                    new ReconciledPackage(request, installArgs, scanResult.mPkgSetting,
+                            res, request.mPreparedPackages.get(installPackageName), scanResult,
+                            deletePackageAction, allowedSharedLibInfos, signingDetails,
+                            sharedUserSignaturesChanged, removeAppKeySetData));
+        }
+
+        for (String installPackageName : scannedPackages.keySet()) {
+            // 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
+            // will take care of the system apps by updating all of their
+            // 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.
+            final ScanResult scanResult = scannedPackages.get(installPackageName);
+            if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
+                    || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+                    != 0) {
+                continue;
+            }
+            try {
+                result.get(installPackageName).mCollectedSharedLibraryInfos =
+                        SharedLibraryHelper.collectSharedLibraryInfos(
+                                scanResult.mRequest.mParsedPackage,
+                                combinedPackages, request.mSharedLibrarySource,
+                                incomingSharedLibraries, injector.getCompatibility());
+
+            } catch (PackageManagerException e) {
+                throw new ReconcileFailure(e.error, e.getMessage());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * If the database version for this type of package (internal storage or
+     * external storage) is less than the version where package signatures
+     * were updated, return true.
+     */
+    public static boolean isCompatSignatureUpdateNeeded(Settings.VersionInfo ver) {
+        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_END_ENTITY;
+    }
+
+    public static boolean isRecoverSignatureUpdateNeeded(Settings.VersionInfo ver) {
+        return ver.databaseVersion < Settings.DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 0ee1f89..2aff90f 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -75,7 +75,7 @@
      * since we need to allow the system to start any installed application.
      */
     public ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags,
+            @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
             boolean resolveForStart, int filterCallingUid) {
         try {
@@ -115,7 +115,7 @@
     }
 
     private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags,
+            @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags,
             List<ResolveInfo> query, int userId, boolean queryMayBeFiltered) {
         if (query != null) {
@@ -278,7 +278,7 @@
     // In this method, we have to know the actual calling UID, but in some cases Binder's
     // call identity is removed, so the UID has to be passed in explicitly.
     public @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId,
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId,
             int filterCallingUid) {
         if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
         mPm.enforceCrossUserPermission(filterCallingUid, userId, false /*requireFullPermission*/,
@@ -374,7 +374,7 @@
 
 
     public ResolveInfo resolveServiceInternal(Intent intent, String resolvedType,
-            @PackageManager.ResolveInfoFlags long flags, int userId, int callingUid) {
+            @PackageManager.ResolveInfoFlagsBits long flags, int userId, int callingUid) {
         if (!mPm.mUserManager.exists(userId)) return null;
         flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
                 false /* isImplicitImageCaptureIntentAndNotSetByDpc */);
@@ -391,7 +391,7 @@
     }
 
     public @NonNull List<ResolveInfo> queryIntentContentProvidersInternal(
-            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlags long flags,
+            Intent intent, String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags,
             int userId) {
         if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
@@ -533,7 +533,7 @@
 
     public @NonNull List<ResolveInfo> queryIntentActivityOptionsInternal(ComponentName caller,
             Intent[] specifics, String[] specificTypes, Intent intent,
-            String resolvedType, @PackageManager.ResolveInfoFlags long flags, int userId) {
+            String resolvedType, @PackageManager.ResolveInfoFlagsBits long flags, int userId) {
         if (!mPm.mUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
         flags = mPm.updateFlagsForResolve(flags, userId, callingUid, false /*includeInstantApps*/,
diff --git a/services/core/java/com/android/server/pm/ScanPackageHelper.java b/services/core/java/com/android/server/pm/ScanPackageHelper.java
deleted file mode 100644
index eafe0d98..0000000
--- a/services/core/java/com/android/server/pm/ScanPackageHelper.java
+++ /dev/null
@@ -1,1716 +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.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
-import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
-import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
-import static android.content.pm.PackageManager.INSTALL_FAILED_PROCESS_NOT_DEFINED;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
-import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-
-import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
-import static com.android.server.pm.PackageManagerService.DEBUG_ABI_SELECTION;
-import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
-import static com.android.server.pm.PackageManagerService.DEBUG_UPGRADE;
-import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-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_AS_ODM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
-import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
-import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
-import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
-import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
-import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
-import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
-import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
-import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
-import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_TIME;
-import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
-import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
-import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
-import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
-import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
-import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.SharedLibraryInfo;
-import android.content.pm.SigningDetails;
-import android.content.pm.parsing.ParsingPackageUtils;
-import android.content.pm.parsing.component.ComponentMutateUtils;
-import android.content.pm.parsing.component.ParsedActivity;
-import android.content.pm.parsing.component.ParsedMainComponent;
-import android.content.pm.parsing.component.ParsedProcess;
-import android.content.pm.parsing.component.ParsedProvider;
-import android.content.pm.parsing.component.ParsedService;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
-import android.os.Build;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.apk.ApkSignatureVerifier;
-import android.util.jar.StrictJarFile;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.security.VerityUtils;
-import com.android.internal.util.ArrayUtils;
-import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.utils.WatchedLongSparseArray;
-
-import dalvik.system.VMRuntime;
-
-import java.io.File;
-import java.io.IOException;
-import java.security.DigestException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.UUID;
-
-/**
- * Helper class that handles package scanning logic
- */
-final class ScanPackageHelper {
-    final PackageManagerService mPm;
-    final PackageManagerServiceInjector mInjector;
-
-    // TODO(b/198166813): remove PMS dependency
-    public ScanPackageHelper(PackageManagerService pm) {
-        mPm = pm;
-        mInjector = pm.mInjector;
-    }
-
-    ScanPackageHelper(PackageManagerService pm, PackageManagerServiceInjector injector) {
-        mPm = pm;
-        mInjector = injector;
-    }
-
-    /**
-     *  Similar to the other scanPackageTracedLI but accepting a ParsedPackage instead of a File.
-     */
-    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public ScanResult scanPackageTracedLI(ParsedPackage parsedPackage,
-            final @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
-            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
-        try {
-            return scanPackageNewLI(parsedPackage, parseFlags, scanFlags, currentTime, user,
-                    cpuAbiOverride);
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    // TODO(b/199937291): scanPackageNewLI() and scanPackageOnlyLI() should be merged.
-    // But, first, committing the results / removing app data needs to be moved up a level to the
-    // callers of this method. Also, we need to solve the problem of potentially creating a new
-    // shared user setting. That can probably be done later and patch things up after the fact.
-    @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
-            final @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
-            @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
-
-        final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
-                AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
-        final String realPkgName = getRealPackageName(parsedPackage, renamedPkgName);
-        if (realPkgName != null) {
-            ensurePackageRenamed(parsedPackage, renamedPkgName);
-        }
-        final PackageSetting originalPkgSetting = getOriginalPackageLocked(parsedPackage,
-                renamedPkgName);
-        final PackageSetting pkgSetting =
-                mPm.mSettings.getPackageLPr(parsedPackage.getPackageName());
-        final PackageSetting disabledPkgSetting =
-                mPm.mSettings.getDisabledSystemPkgLPr(parsedPackage.getPackageName());
-
-        if (mPm.mTransferredPackages.contains(parsedPackage.getPackageName())) {
-            Slog.w(TAG, "Package " + parsedPackage.getPackageName()
-                    + " was transferred to another, but its .apk remains");
-        }
-
-        scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user, parsedPackage);
-        synchronized (mPm.mLock) {
-            boolean isUpdatedSystemApp;
-            if (pkgSetting != null) {
-                isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
-            } else {
-                isUpdatedSystemApp = disabledPkgSetting != null;
-            }
-            applyPolicy(parsedPackage, scanFlags, mPm.getPlatformPackage(), isUpdatedSystemApp);
-            assertPackageIsValid(parsedPackage, parseFlags, scanFlags);
-
-            SharedUserSetting sharedUserSetting = null;
-            if (parsedPackage.getSharedUserId() != null) {
-                // SIDE EFFECTS; may potentially allocate a new shared user
-                sharedUserSetting = mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
-                        0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
-                if (DEBUG_PACKAGE_SCANNING) {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
-                        Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
-                                + " (uid=" + sharedUserSetting.userId + "):"
-                                + " packages=" + sharedUserSetting.packages);
-                    }
-                }
-            }
-            String platformPackageName = mPm.getPlatformPackage() == null
-                    ? null : mPm.getPlatformPackage().getPackageName();
-            final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting,
-                    pkgSetting == null ? null : pkgSetting.getPkg(), pkgSetting, disabledPkgSetting,
-                    originalPkgSetting, realPkgName, parseFlags, scanFlags,
-                    Objects.equals(parsedPackage.getPackageName(), platformPackageName), user,
-                    cpuAbiOverride);
-            return scanPackageOnlyLI(request, mInjector, mPm.mFactoryTest, currentTime);
-        }
-    }
-
-    /**
-     * Just scans the package without any side effects.
-     * <p>Not entirely true at the moment. There is still one side effect -- this
-     * method potentially modifies a live {@link PackageSetting} object representing
-     * the package being scanned. This will be resolved in the future.
-     *
-     * @param injector injector for acquiring dependencies
-     * @param request Information about the package to be scanned
-     * @param isUnderFactoryTest Whether or not the device is under factory test
-     * @param currentTime The current time, in millis
-     * @return The results of the scan
-     */
-    @GuardedBy("mPm.mInstallLock")
-    @VisibleForTesting
-    @NonNull
-    public ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
-            PackageManagerServiceInjector injector,
-            boolean isUnderFactoryTest, long currentTime)
-            throws PackageManagerException {
-        final PackageAbiHelper packageAbiHelper = injector.getAbiHelper();
-        ParsedPackage parsedPackage = request.mParsedPackage;
-        PackageSetting pkgSetting = request.mPkgSetting;
-        final PackageSetting disabledPkgSetting = request.mDisabledPkgSetting;
-        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
-        final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
-        final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
-        final String realPkgName = request.mRealPkgName;
-        final SharedUserSetting sharedUserSetting = request.mSharedUserSetting;
-        final UserHandle user = request.mUser;
-        final boolean isPlatformPackage = request.mIsPlatformPackage;
-
-        List<String> changedAbiCodePath = null;
-
-        if (DEBUG_PACKAGE_SCANNING) {
-            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
-                Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
-            }
-        }
-
-        // Initialize package source and resource directories
-        final File destCodeFile = new File(parsedPackage.getPath());
-
-        // We keep references to the derived CPU Abis from settings in oder to reuse
-        // them in the case where we're not upgrading or booting for the first time.
-        String primaryCpuAbiFromSettings = null;
-        String secondaryCpuAbiFromSettings = null;
-        boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
-        if (!needToDeriveAbi) {
-            if (pkgSetting != null) {
-                // TODO(b/154610922): if it is not first boot or upgrade, we should directly use
-                // API info from existing package setting. However, stub packages currently do not
-                // preserve ABI info, thus the special condition check here. Remove the special
-                // check after we fix the stub generation.
-                if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
-                    needToDeriveAbi = true;
-                } else {
-                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbi();
-                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbi();
-                }
-            } else {
-                // Re-scanning a system package after uninstalling updates; need to derive ABI
-                needToDeriveAbi = true;
-            }
-        }
-
-        int previousAppId = Process.INVALID_UID;
-
-        if (pkgSetting != null && pkgSetting.getSharedUser() != sharedUserSetting) {
-            if (pkgSetting.getSharedUser() != null && sharedUserSetting == null) {
-                previousAppId = pkgSetting.getAppId();
-                // Log that something is leaving shareduid and keep going
-                Slog.i(TAG,
-                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
-                                + pkgSetting.getSharedUser().name + " to " + "<nothing>.");
-            } else {
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
-                                + (pkgSetting.getSharedUser() != null
-                                ? pkgSetting.getSharedUser().name : "<nothing>")
-                                + " to "
-                                + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
-                                + "; replacing with new");
-                pkgSetting = null;
-            }
-        }
-
-        String[] usesSdkLibraries = null;
-        if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
-            usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
-            parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
-        }
-
-        String[] usesStaticLibraries = null;
-        if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
-            usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
-            parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
-        }
-
-        final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
-
-        // TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
-        //  to avoid adding something that's unsupported due to lack of state, since it's called
-        //  with null.
-        final boolean createNewPackage = (pkgSetting == null);
-        if (createNewPackage) {
-            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
-            final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
-
-            // Flags contain system values stored in the server variant of AndroidPackage,
-            // and so the server-side PackageInfoUtils is still called, even without a
-            // PackageSetting to pass in.
-            int pkgFlags = PackageInfoUtils.appInfoFlags(parsedPackage, null);
-            int pkgPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(parsedPackage, null);
-
-            // REMOVE SharedUserSetting from method; update in a separate call
-            pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
-                    originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
-                    destCodeFile, parsedPackage.getNativeLibraryRootDir(),
-                    AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
-                    AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
-                    parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
-                    true /*allowInstall*/, instantApp, virtualPreload,
-                    UserManagerService.getInstance(), usesSdkLibraries,
-                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
-                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
-                    newDomainSetId);
-        } else {
-            // make a deep copy to avoid modifying any existing system state.
-            pkgSetting = new PackageSetting(pkgSetting);
-            pkgSetting.setPkg(parsedPackage);
-
-            // REMOVE SharedUserSetting from method; update in a separate call.
-            //
-            // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi,
-            // secondaryCpuAbi are not known at this point so we always update them
-            // to null here, only to reset them at a later point.
-            Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
-                    destCodeFile, parsedPackage.getNativeLibraryDir(),
-                    AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
-                    AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
-                    PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
-                    PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
-                    UserManagerService.getInstance(),
-                    usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
-                    usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
-                    parsedPackage.getMimeGroups(), newDomainSetId);
-        }
-        if (createNewPackage && originalPkgSetting != null) {
-            // This is the initial transition from the original package, so,
-            // fix up the new package's name now. We must do this after looking
-            // up the package under its new name, so getPackageLP takes care of
-            // fiddling things correctly.
-            parsedPackage.setPackageName(originalPkgSetting.getPackageName());
-
-            // File a report about this.
-            String msg = "New package " + pkgSetting.getRealName()
-                    + " renamed to replace old package " + pkgSetting.getPackageName();
-            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
-        }
-
-        final int userId = (user == null ? UserHandle.USER_SYSTEM : user.getIdentifier());
-        // for existing packages, change the install state; but, only if it's explicitly specified
-        if (!createNewPackage) {
-            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
-            final boolean fullApp = (scanFlags & SCAN_AS_FULL_APP) != 0;
-            PackageManagerService.setInstantAppForUser(
-                    injector, pkgSetting, userId, instantApp, fullApp);
-        }
-        // TODO(patb): see if we can do away with disabled check here.
-        if (disabledPkgSetting != null
-                || (0 != (scanFlags & SCAN_NEW_INSTALL)
-                && pkgSetting != null && pkgSetting.isSystem())) {
-            pkgSetting.getPkgState().setUpdatedSystemApp(true);
-        }
-
-        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
-                injector.getCompatibility()));
-
-        if (parsedPackage.isSystem()) {
-            configurePackageComponents(parsedPackage);
-        }
-
-        final String cpuAbiOverride = deriveAbiOverride(request.mCpuAbiOverride);
-        final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
-
-        final File appLib32InstallDir = PackageManagerService.getAppLib32InstallDir();
-        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
-            if (needToDeriveAbi) {
-                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
-                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
-                        packageAbiHelper.derivePackageAbi(parsedPackage, isUpdatedSystemApp,
-                                cpuAbiOverride, appLib32InstallDir);
-                derivedAbi.first.applyTo(parsedPackage);
-                derivedAbi.second.applyTo(parsedPackage);
-                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-
-                // Some system apps still use directory structure for native libraries
-                // in which case we might end up not detecting abi solely based on apk
-                // structure. Try to detect abi based on directory structure.
-
-                String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
-                if (parsedPackage.isSystem() && !isUpdatedSystemApp
-                        && pkgRawPrimaryCpuAbi == null) {
-                    final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
-                            parsedPackage);
-                    abis.applyTo(parsedPackage);
-                    abis.applyTo(pkgSetting);
-                    final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
-                                    isUpdatedSystemApp, appLib32InstallDir);
-                    nativeLibraryPaths.applyTo(parsedPackage);
-                }
-            } else {
-                // This is not a first boot or an upgrade, don't bother deriving the
-                // ABI during the scan. Instead, trust the value that was stored in the
-                // package setting.
-                parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
-                        .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
-
-                final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
-                                isUpdatedSystemApp, appLib32InstallDir);
-                nativeLibraryPaths.applyTo(parsedPackage);
-
-                if (DEBUG_ABI_SELECTION) {
-                    Slog.i(TAG, "Using ABIS and native lib paths from settings : "
-                            + parsedPackage.getPackageName() + " "
-                            + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
-                            + ", "
-                            + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
-                }
-            }
-        } else {
-            if ((scanFlags & SCAN_MOVE) != 0) {
-                // We haven't run dex-opt for this move (since we've moved the compiled output too)
-                // but we already have this packages package info in the PackageSetting. We just
-                // use that and derive the native library path based on the new code path.
-                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbi())
-                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbi());
-            }
-
-            // Set native library paths again. For moves, the path will be updated based on the
-            // ABIs we've determined above. For non-moves, the path will be updated based on the
-            // ABIs we determined during compilation, but the path will depend on the final
-            // package path (after the rename away from the stage path).
-            final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isUpdatedSystemApp,
-                            appLib32InstallDir);
-            nativeLibraryPaths.applyTo(parsedPackage);
-        }
-
-        // This is a special case for the "system" package, where the ABI is
-        // dictated by the zygote configuration (and init.rc). We should keep track
-        // of this ABI so that we can deal with "normal" applications that run under
-        // the same UID correctly.
-        if (isPlatformPackage) {
-            parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
-                    ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
-        }
-
-        // If there's a mismatch between the abi-override in the package setting
-        // and the abiOverride specified for the install. Warn about this because we
-        // would've already compiled the app without taking the package setting into
-        // account.
-        if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
-            if (cpuAbiOverride == null) {
-                Slog.w(TAG, "Ignoring persisted ABI override for package "
-                        + parsedPackage.getPackageName());
-            }
-        }
-
-        pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
-                .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
-                .setCpuAbiOverride(cpuAbiOverride);
-
-        if (DEBUG_ABI_SELECTION) {
-            Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
-                    + " to root=" + parsedPackage.getNativeLibraryRootDir()
-                    + ", to dir=" + parsedPackage.getNativeLibraryDir()
-                    + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
-        }
-
-        // Push the derived path down into PackageSettings so we know what to
-        // clean up at uninstall time.
-        pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());
-
-        if (DEBUG_ABI_SELECTION) {
-            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
-                    + " primary=" + pkgSetting.getPrimaryCpuAbi()
-                    + " secondary=" + pkgSetting.getSecondaryCpuAbi()
-                    + " abiOverride=" + pkgSetting.getCpuAbiOverride());
-        }
-
-        if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.getSharedUser() != null) {
-            // We don't do this here during boot because we can do it all
-            // at once after scanning all existing packages.
-            //
-            // We also do this *before* we perform dexopt on this package, so that
-            // we can avoid redundant dexopts, and also to make sure we've got the
-            // code and package path correct.
-            changedAbiCodePath = applyAdjustedAbiToSharedUser(pkgSetting.getSharedUser(),
-                    parsedPackage, packageAbiHelper.getAdjustedAbiForSharedUser(
-                            pkgSetting.getSharedUser().packages, parsedPackage));
-        }
-
-        parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions()
-                .contains(android.Manifest.permission.FACTORY_TEST));
-
-        if (parsedPackage.isSystem()) {
-            pkgSetting.setIsOrphaned(true);
-        }
-
-        // Take care of first install / last update times.
-        final long scanFileTime = getLastModifiedTime(parsedPackage);
-        if (currentTime != 0) {
-            if (pkgSetting.getFirstInstallTime() == 0) {
-                pkgSetting.setFirstInstallTime(currentTime)
-                        .setLastUpdateTime(currentTime);
-            } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
-                pkgSetting.setLastUpdateTime(currentTime);
-            }
-        } else if (pkgSetting.getFirstInstallTime() == 0) {
-            // We need *something*.  Take time time stamp of the file.
-            pkgSetting.setFirstInstallTime(scanFileTime)
-                    .setLastUpdateTime(scanFileTime);
-        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
-            if (scanFileTime != pkgSetting.getLastModifiedTime()) {
-                // A package on the system image has changed; consider this
-                // to be an update.
-                pkgSetting.setLastUpdateTime(scanFileTime);
-            }
-        }
-        pkgSetting.setLastModifiedTime(scanFileTime);
-        // TODO(b/135203078): Remove, move to constructor
-        pkgSetting.setPkg(parsedPackage)
-                .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
-                .setPrivateFlags(
-                        PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
-        if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
-            pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
-        }
-        // Update volume if needed
-        final String volumeUuid = parsedPackage.getVolumeUuid();
-        if (!Objects.equals(volumeUuid, pkgSetting.getVolumeUuid())) {
-            Slog.i(PackageManagerService.TAG,
-                    "Update" + (pkgSetting.isSystem() ? " system" : "")
-                            + " package " + parsedPackage.getPackageName()
-                            + " volume from " + pkgSetting.getVolumeUuid()
-                            + " to " + volumeUuid);
-            pkgSetting.setVolumeUuid(volumeUuid);
-        }
-
-        SharedLibraryInfo sdkLibraryInfo = null;
-        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
-            sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
-        }
-        SharedLibraryInfo staticSharedLibraryInfo = null;
-        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
-            staticSharedLibraryInfo =
-                    AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
-        }
-        List<SharedLibraryInfo> dynamicSharedLibraryInfos = null;
-        if (!ArrayUtils.isEmpty(parsedPackage.getLibraryNames())) {
-            dynamicSharedLibraryInfos = new ArrayList<>(parsedPackage.getLibraryNames().size());
-            for (String name : parsedPackage.getLibraryNames()) {
-                dynamicSharedLibraryInfos.add(
-                        AndroidPackageUtils.createSharedLibraryForDynamic(parsedPackage, name));
-            }
-        }
-
-        return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
-                !createNewPackage /* existingSettingCopied */,
-                previousAppId, sdkLibraryInfo, staticSharedLibraryInfo,
-                dynamicSharedLibraryInfos);
-    }
-
-    /**
-     * Returns the actual scan flags depending upon the state of the other settings.
-     * <p>Updated system applications will not have the following flags set
-     * by default and need to be adjusted after the fact:
-     * <ul>
-     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_PRIVILEGED}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_OEM}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_VENDOR}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_PRODUCT}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM_EXT}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_INSTANT_APP}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD}</li>
-     * <li>{@link PackageManagerService.SCAN_AS_ODM}</li>
-     * </ul>
-     */
-    private @PackageManagerService.ScanFlags int adjustScanFlags(
-            @PackageManagerService.ScanFlags int scanFlags,
-            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user,
-            AndroidPackage pkg) {
-
-        // TODO(patb): Do away entirely with disabledPkgSetting here. PkgSetting will always contain
-        // the correct isSystem value now that we don't disable system packages before scan.
-        final PackageSetting systemPkgSetting =
-                (scanFlags & SCAN_NEW_INSTALL) != 0 && disabledPkgSetting == null
-                        && pkgSetting != null && pkgSetting.isSystem()
-                        ? pkgSetting
-                        : disabledPkgSetting;
-        if (systemPkgSetting != null)  {
-            // updated system application, must at least have SCAN_AS_SYSTEM
-            scanFlags |= SCAN_AS_SYSTEM;
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
-                scanFlags |= SCAN_AS_PRIVILEGED;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
-                scanFlags |= SCAN_AS_OEM;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
-                scanFlags |= SCAN_AS_VENDOR;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0) {
-                scanFlags |= SCAN_AS_PRODUCT;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
-                scanFlags |= SCAN_AS_SYSTEM_EXT;
-            }
-            if ((systemPkgSetting.getPrivateFlags()
-                    & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
-                scanFlags |= SCAN_AS_ODM;
-            }
-        }
-        if (pkgSetting != null) {
-            final int userId = ((user == null) ? 0 : user.getIdentifier());
-            if (pkgSetting.getInstantApp(userId)) {
-                scanFlags |= SCAN_AS_INSTANT_APP;
-            }
-            if (pkgSetting.getVirtualPreload(userId)) {
-                scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
-            }
-        }
-
-        // Scan as privileged apps that share a user with a priv-app.
-        final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
-                && getVendorPartitionVersion() < 28;
-        if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
-                && !pkg.isPrivileged()
-                && (pkg.getSharedUserId() != null)
-                && !skipVendorPrivilegeScan) {
-            SharedUserSetting sharedUserSetting = null;
-            try {
-                sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(), 0,
-                        0, false);
-            } catch (PackageManagerException ignore) {
-            }
-            if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
-                // Exempt SharedUsers signed with the platform key.
-                // TODO(b/72378145) Fix this exemption. Force signature apps
-                // to allowlist their privileged permissions just like other
-                // priv-apps.
-                synchronized (mPm.mLock) {
-                    PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
-                    if ((compareSignatures(
-                            platformPkgSetting.getSigningDetails().getSignatures(),
-                            pkg.getSigningDetails().getSignatures())
-                            != PackageManager.SIGNATURE_MATCH)) {
-                        scanFlags |= SCAN_AS_PRIVILEGED;
-                    }
-                }
-            }
-        }
-
-        return scanFlags;
-    }
-
-    public Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
-            @ParsingPackageUtils.ParseFlags int parseFlags,
-            @PackageManagerService.ScanFlags int scanFlags, long currentTime,
-            @Nullable UserHandle user) throws PackageManagerException {
-        final boolean scanSystemPartition =
-                (parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0;
-        final String renamedPkgName;
-        final PackageSetting disabledPkgSetting;
-        final boolean isSystemPkgUpdated;
-        final boolean pkgAlreadyExists;
-        PackageSetting pkgSetting;
-        AndroidPackage platformPackage;
-        final boolean isUpgrade = mPm.isDeviceUpgrading();
-
-        synchronized (mPm.mLock) {
-            platformPackage = mPm.getPlatformPackage();
-            renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
-                    AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
-            final String realPkgName = getRealPackageName(parsedPackage,
-                    renamedPkgName);
-            if (realPkgName != null) {
-                ensurePackageRenamed(parsedPackage, renamedPkgName);
-            }
-            final PackageSetting originalPkgSetting = getOriginalPackageLocked(parsedPackage,
-                    renamedPkgName);
-            final PackageSetting installedPkgSetting = mPm.mSettings.getPackageLPr(
-                    parsedPackage.getPackageName());
-            pkgSetting = originalPkgSetting == null ? installedPkgSetting : originalPkgSetting;
-            pkgAlreadyExists = pkgSetting != null;
-            final String disabledPkgName = pkgAlreadyExists
-                    ? pkgSetting.getPackageName() : parsedPackage.getPackageName();
-            if (scanSystemPartition && !pkgAlreadyExists
-                    && mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName) != null) {
-                // The updated-package data for /system apk remains inconsistently
-                // after the package data for /data apk is lost accidentally.
-                // To recover it, enable /system apk and install it as non-updated system app.
-                Slog.w(TAG, "Inconsistent package setting of updated system app for "
-                        + disabledPkgName + ". To recover it, enable the system app"
-                        + "and install it as non-updated system app.");
-                mPm.mSettings.removeDisabledSystemPackageLPw(disabledPkgName);
-            }
-            disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(disabledPkgName);
-            isSystemPkgUpdated = disabledPkgSetting != null;
-
-            if (DEBUG_INSTALL && isSystemPkgUpdated) {
-                Slog.d(TAG, "updatedPkg = " + disabledPkgSetting);
-            }
-
-            final SharedUserSetting sharedUserSetting = (parsedPackage.getSharedUserId() != null)
-                    ? mPm.mSettings.getSharedUserLPw(parsedPackage.getSharedUserId(),
-                    0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true)
-                    : null;
-            if (DEBUG_PACKAGE_SCANNING
-                    && (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0
-                    && sharedUserSetting != null) {
-                Log.d(TAG, "Shared UserID " + parsedPackage.getSharedUserId()
-                        + " (uid=" + sharedUserSetting.userId + "):"
-                        + " packages=" + sharedUserSetting.packages);
-            }
-
-            if (scanSystemPartition) {
-                if (isSystemPkgUpdated) {
-                    // we're updating the disabled package, so, scan it as the package setting
-                    boolean isPlatformPackage = platformPackage != null
-                            && platformPackage.getPackageName().equals(
-                            parsedPackage.getPackageName());
-                    final ScanRequest request = new ScanRequest(parsedPackage, sharedUserSetting,
-                            null, disabledPkgSetting /* pkgSetting */,
-                            null /* disabledPkgSetting */, null /* originalPkgSetting */,
-                            null, parseFlags, scanFlags, isPlatformPackage, user, null);
-                    applyPolicy(parsedPackage, scanFlags,
-                            platformPackage, true);
-                    final ScanResult scanResult =
-                            scanPackageOnlyLI(request, mInjector,
-                                    mPm.mFactoryTest, -1L);
-                    if (scanResult.mExistingSettingCopied
-                            && scanResult.mRequest.mPkgSetting != null) {
-                        scanResult.mRequest.mPkgSetting.updateFrom(scanResult.mPkgSetting);
-                    }
-                }
-            }
-        }
-
-        final boolean newPkgChangedPaths = pkgAlreadyExists
-                && !pkgSetting.getPathString().equals(parsedPackage.getPath());
-        final boolean newPkgVersionGreater = pkgAlreadyExists
-                && parsedPackage.getLongVersionCode() > pkgSetting.getVersionCode();
-        final boolean isSystemPkgBetter = scanSystemPartition && isSystemPkgUpdated
-                && newPkgChangedPaths && newPkgVersionGreater;
-        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.
-            synchronized (mPm.mLock) {
-                // just remove the loaded entries from package lists
-                mPm.mPackages.remove(pkgSetting.getPackageName());
-            }
-
-            logCriticalInfo(Log.WARN,
-                    "System package updated;"
-                            + " name: " + pkgSetting.getPackageName()
-                            + "; " + pkgSetting.getVersionCode() + " --> "
-                            + parsedPackage.getLongVersionCode()
-                            + "; " + pkgSetting.getPathString()
-                            + " --> " + parsedPackage.getPath());
-
-            final InstallArgs args = new FileInstallArgs(
-                    pkgSetting.getPathString(), getAppDexInstructionSets(
-                    pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()), mPm);
-            args.cleanUpResourcesLI();
-            synchronized (mPm.mLock) {
-                mPm.mSettings.enableSystemPackageLPw(pkgSetting.getPackageName());
-            }
-        }
-
-        // The version of the application on the /system partition is less than or
-        // equal to the version on the /data partition. Throw an exception and use
-        // the application already installed on the /data partition.
-        if (scanSystemPartition && isSystemPkgUpdated && !isSystemPkgBetter) {
-            // In the case of a skipped package, commitReconciledScanResultLocked is not called to
-            // add the object to the "live" data structures, so this is the final mutation step
-            // for the package. Which means it needs to be finalized here to cache derived fields.
-            // This is relevant for cases where the disabled system package is used for flags or
-            // other metadata.
-            parsedPackage.hideAsFinal();
-            throw new PackageManagerException(Log.WARN, "Package " + parsedPackage.getPackageName()
-                    + " at " + parsedPackage.getPath() + " ignored: updated version "
-                    + (pkgAlreadyExists ? String.valueOf(pkgSetting.getVersionCode()) : "unknown")
-                    + " better than this " + parsedPackage.getLongVersionCode());
-        }
-
-        // Verify certificates against what was last scanned. Force re-collecting certificate in two
-        // special cases:
-        // 1) when scanning system, force re-collect only if system is upgrading.
-        // 2) when scannning /data, force re-collect only if the app is privileged (updated from
-        // preinstall, or treated as privileged, e.g. due to shared user ID).
-        final boolean forceCollect = scanSystemPartition ? isUpgrade
-                : PackageManagerServiceUtils.isApkVerificationForced(pkgSetting);
-        if (DEBUG_VERIFY && forceCollect) {
-            Slog.d(TAG, "Force collect certificate of " + parsedPackage.getPackageName());
-        }
-
-        // Full APK verification can be skipped during certificate collection, only if the file is
-        // in verified partition, or can be verified on access (when apk verity is enabled). In both
-        // cases, only data in Signing Block is verified instead of the whole file.
-        // TODO(b/136132412): skip for Incremental installation
-        final boolean skipVerify = scanSystemPartition
-                || (forceCollect && canSkipForcedPackageVerification(parsedPackage));
-        collectCertificatesLI(pkgSetting, parsedPackage, forceCollect, skipVerify);
-
-        // Reset profile if the application version is changed
-        maybeClearProfilesForUpgradesLI(pkgSetting, parsedPackage);
-
-        /*
-         * A new system app appeared, but we already had a non-system one of the
-         * same name installed earlier.
-         */
-        boolean shouldHideSystemApp = false;
-        // A new application appeared on /system, but, we already have a copy of
-        // the application installed on /data.
-        if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists
-                && !pkgSetting.isSystem()) {
-
-            if (!parsedPackage.getSigningDetails()
-                    .checkCapability(pkgSetting.getSigningDetails(),
-                            SigningDetails.CertCapabilities.INSTALLED_DATA)
-                    && !pkgSetting.getSigningDetails().checkCapability(
-                    parsedPackage.getSigningDetails(),
-                    SigningDetails.CertCapabilities.ROLLBACK)) {
-                logCriticalInfo(Log.WARN,
-                        "System package signature mismatch;"
-                                + " name: " + pkgSetting.getPackageName());
-                try (@SuppressWarnings("unused") PackageFreezer freezer = mPm.freezePackage(
-                        parsedPackage.getPackageName(),
-                        "scanPackageInternalLI")) {
-                    DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
-                    deletePackageHelper.deletePackageLIF(parsedPackage.getPackageName(), null, true,
-                            mPm.mUserManager.getUserIds(), 0, null, false);
-                }
-                pkgSetting = null;
-            } else if (newPkgVersionGreater) {
-                // The application on /system is newer than the application on /data.
-                // Simply remove the application on /data [keeping application data]
-                // and replace it with the version on /system.
-                logCriticalInfo(Log.WARN,
-                        "System package enabled;"
-                                + " name: " + pkgSetting.getPackageName()
-                                + "; " + pkgSetting.getVersionCode() + " --> "
-                                + parsedPackage.getLongVersionCode()
-                                + "; " + pkgSetting.getPathString() + " --> "
-                                + parsedPackage.getPath());
-                InstallArgs args = new FileInstallArgs(
-                        pkgSetting.getPathString(), getAppDexInstructionSets(
-                        pkgSetting.getPrimaryCpuAbi(), pkgSetting.getSecondaryCpuAbi()),
-                        mPm);
-                synchronized (mPm.mInstallLock) {
-                    args.cleanUpResourcesLI();
-                }
-            } else {
-                // The application on /system is older than the application on /data. Hide
-                // the application on /system and the version on /data will be scanned later
-                // and re-added like an update.
-                shouldHideSystemApp = true;
-                logCriticalInfo(Log.INFO,
-                        "System package disabled;"
-                                + " name: " + pkgSetting.getPackageName()
-                                + "; old: " + pkgSetting.getPathString() + " @ "
-                                + pkgSetting.getVersionCode()
-                                + "; new: " + parsedPackage.getPath() + " @ "
-                                + parsedPackage.getPath());
-            }
-        }
-
-        final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
-                scanFlags | SCAN_UPDATE_SIGNATURE, currentTime, user, null);
-        return new Pair<>(scanResult, shouldHideSystemApp);
-    }
-
-    /**
-     * Returns if forced apk verification can be skipped for the whole package, including splits.
-     */
-    private boolean canSkipForcedPackageVerification(AndroidPackage pkg) {
-        final String packageName = pkg.getPackageName();
-        if (!canSkipForcedApkVerification(packageName, pkg.getBaseApkPath())) {
-            return false;
-        }
-        // TODO: Allow base and splits to be verified individually.
-        String[] splitCodePaths = pkg.getSplitCodePaths();
-        if (!ArrayUtils.isEmpty(splitCodePaths)) {
-            for (int i = 0; i < splitCodePaths.length; i++) {
-                if (!canSkipForcedApkVerification(packageName, splitCodePaths[i])) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Returns if forced apk verification can be skipped, depending on current FSVerity setup and
-     * whether the apk contains signed root hash.  Note that the signer's certificate still needs to
-     * match one in a trusted source, and should be done separately.
-     */
-    private boolean canSkipForcedApkVerification(String packageName, String apkPath) {
-        if (!PackageManagerServiceUtils.isLegacyApkVerityEnabled()) {
-            return VerityUtils.hasFsverity(apkPath);
-        }
-
-        try {
-            final byte[] rootHashObserved = VerityUtils.generateApkVerityRootHash(apkPath);
-            if (rootHashObserved == null) {
-                return false;  // APK does not contain Merkle tree root hash.
-            }
-            synchronized (mPm.mInstallLock) {
-                // Returns whether the observed root hash matches what kernel has.
-                mPm.mInstaller.assertFsverityRootHashMatches(packageName, apkPath,
-                        rootHashObserved);
-                return true;
-            }
-        } catch (Installer.InstallerException | IOException | DigestException
-                | NoSuchAlgorithmException e) {
-            Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
-        }
-        return false;
-    }
-
-    private void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
-            boolean forceCollect, boolean skipVerify) throws PackageManagerException {
-        // When upgrading from pre-N MR1, verify the package time stamp using the package
-        // directory and not the APK file.
-        final long lastModifiedTime = mPm.isPreNMR1Upgrade()
-                ? new File(parsedPackage.getPath()).lastModified()
-                : getLastModifiedTime(parsedPackage);
-        final Settings.VersionInfo settingsVersionForPackage =
-                mPm.getSettingsVersionForPackage(parsedPackage);
-        if (ps != null && !forceCollect
-                && ps.getPathString().equals(parsedPackage.getPath())
-                && ps.getLastModifiedTime() == lastModifiedTime
-                && !InstallPackageHelper.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
-                && !InstallPackageHelper.isRecoverSignatureUpdateNeeded(
-                settingsVersionForPackage)) {
-            if (ps.getSigningDetails().getSignatures() != null
-                    && ps.getSigningDetails().getSignatures().length != 0
-                    && ps.getSigningDetails().getSignatureSchemeVersion()
-                    != SigningDetails.SignatureSchemeVersion.UNKNOWN) {
-                // Optimization: reuse the existing cached signing data
-                // if the package appears to be unchanged.
-                parsedPackage.setSigningDetails(
-                        new SigningDetails(ps.getSigningDetails()));
-                return;
-            }
-
-            Slog.w(TAG, "PackageSetting for " + ps.getPackageName()
-                    + " is missing signatures.  Collecting certs again to recover them.");
-        } else {
-            Slog.i(TAG, parsedPackage.getPath() + " changed; collecting certs"
-                    + (forceCollect ? " (forced)" : ""));
-        }
-
-        try {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
-            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
-                    input, parsedPackage, skipVerify);
-            if (result.isError()) {
-                throw new PackageManagerException(
-                        result.getErrorCode(), result.getErrorMessage(), result.getException());
-            }
-            parsedPackage.setSigningDetails(result.getResult());
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
-    /**
-     * Clear the package profile if this was an upgrade and the package
-     * version was updated.
-     */
-    private void maybeClearProfilesForUpgradesLI(
-            @Nullable PackageSetting originalPkgSetting,
-            @NonNull AndroidPackage pkg) {
-        if (originalPkgSetting == null || !mPm.isDeviceUpgrading()) {
-            return;
-        }
-        if (originalPkgSetting.getVersionCode() == pkg.getLongVersionCode()) {
-            return;
-        }
-
-        final AppDataHelper appDataHelper = new AppDataHelper(mPm);
-        appDataHelper.clearAppProfilesLIF(pkg);
-        if (DEBUG_INSTALL) {
-            Slog.d(TAG, originalPkgSetting.getPackageName()
-                    + " clear profile due to version change "
-                    + originalPkgSetting.getVersionCode() + " != "
-                    + pkg.getLongVersionCode());
-        }
-    }
-
-    /**
-     * Returns the original package setting.
-     * <p>A package can migrate its name during an update. In this scenario, a package
-     * designates a set of names that it considers as one of its original names.
-     * <p>An original package must be signed identically and it must have the same
-     * shared user [if any].
-     */
-    @GuardedBy("mPm.mLock")
-    @Nullable
-    private PackageSetting getOriginalPackageLocked(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
-        if (isPackageRenamed(pkg, renamedPkgName)) {
-            return null;
-        }
-        for (int i = ArrayUtils.size(pkg.getOriginalPackages()) - 1; i >= 0; --i) {
-            final PackageSetting originalPs =
-                    mPm.mSettings.getPackageLPr(pkg.getOriginalPackages().get(i));
-            if (originalPs != null) {
-                // the package is already installed under its original name...
-                // but, should we use it?
-                if (!verifyPackageUpdateLPr(originalPs, pkg)) {
-                    // the new package is incompatible with the original
-                    continue;
-                } else if (originalPs.getSharedUser() != null) {
-                    if (!originalPs.getSharedUser().name.equals(pkg.getSharedUserId())) {
-                        // the shared user id is incompatible with the original
-                        Slog.w(TAG, "Unable to migrate data from " + originalPs.getPackageName()
-                                + " to " + pkg.getPackageName() + ": old uid "
-                                + originalPs.getSharedUser().name
-                                + " differs from " + pkg.getSharedUserId());
-                        continue;
-                    }
-                    // TODO: Add case when shared user id is added [b/28144775]
-                } else {
-                    if (DEBUG_UPGRADE) {
-                        Log.v(TAG, "Renaming new package "
-                                + pkg.getPackageName() + " to old name "
-                                + originalPs.getPackageName());
-                    }
-                }
-                return originalPs;
-            }
-        }
-        return null;
-    }
-
-    @GuardedBy("mPm.mLock")
-    private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, AndroidPackage newPkg) {
-        if ((oldPkg.getFlags() & ApplicationInfo.FLAG_SYSTEM) == 0) {
-            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
-                    + " to " + newPkg.getPackageName()
-                    + ": old package not in system partition");
-            return false;
-        } else if (mPm.mPackages.get(oldPkg.getPackageName()) != null) {
-            Slog.w(TAG, "Unable to update from " + oldPkg.getPackageName()
-                    + " to " + newPkg.getPackageName()
-                    + ": old package still exists");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Asserts the parsed package is valid according to the given policy. If the
-     * package is invalid, for whatever reason, throws {@link PackageManagerException}.
-     * <p>
-     * Implementation detail: This method must NOT have any side effects. It would
-     * ideally be static, but, it requires locks to read system state.
-     *
-     * @throws PackageManagerException If the package fails any of the validation checks
-     */
-    private void assertPackageIsValid(AndroidPackage pkg,
-            final @ParsingPackageUtils.ParseFlags int parseFlags,
-            final @PackageManagerService.ScanFlags int scanFlags)
-            throws PackageManagerException {
-        if ((parseFlags & ParsingPackageUtils.PARSE_ENFORCE_CODE) != 0) {
-            assertCodePolicy(pkg);
-        }
-
-        if (pkg.getPath() == null) {
-            // Bail out. The resource and code paths haven't been set.
-            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                    "Code and resource paths haven't been set correctly");
-        }
-
-        // Check that there is an APEX package with the same name only during install/first boot
-        // after OTA.
-        final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0;
-        final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
-        if ((isUserInstall || isFirstBootOrUpgrade)
-                && mPm.mApexManager.isApexPackage(pkg.getPackageName())) {
-            throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                    pkg.getPackageName()
-                            + " is an APEX package and can't be installed as an APK.");
-        }
-
-        // Make sure we're not adding any bogus keyset info
-        final KeySetManagerService ksms = mPm.mSettings.getKeySetManagerService();
-        ksms.assertScannedPackageValid(pkg);
-
-        synchronized (mPm.mLock) {
-            // The special "android" package can only be defined once
-            if (pkg.getPackageName().equals("android")) {
-                if (mPm.getCoreAndroidApplication() != null) {
-                    Slog.w(TAG, "*************************************************");
-                    Slog.w(TAG, "Core android package being redefined.  Skipping.");
-                    Slog.w(TAG, " codePath=" + pkg.getPath());
-                    Slog.w(TAG, "*************************************************");
-                    throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                            "Core android package being redefined.  Skipping.");
-                }
-            }
-
-            // A package name must be unique; don't allow duplicates
-            if ((scanFlags & SCAN_NEW_INSTALL) == 0
-                    && mPm.mPackages.containsKey(pkg.getPackageName())) {
-                throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
-                        "Application package " + pkg.getPackageName()
-                                + " already installed.  Skipping duplicate.");
-            }
-
-            if (pkg.isStaticSharedLibrary()) {
-                // Static libs have a synthetic package name containing the version
-                // but we still want the base name to be unique.
-                if ((scanFlags & SCAN_NEW_INSTALL) == 0
-                        && mPm.mPackages.containsKey(pkg.getManifestPackageName())) {
-                    throw new PackageManagerException(
-                            "Duplicate static shared lib provider package");
-                }
-
-                // Static shared libraries should have at least O target SDK
-                if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs must target O SDK or higher");
-                }
-
-                // Package declaring static a shared lib cannot be instant apps
-                if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot be instant apps");
-                }
-
-                // Package declaring static a shared lib cannot be renamed since the package
-                // name is synthetic and apps can't code around package manager internals.
-                if (!ArrayUtils.isEmpty(pkg.getOriginalPackages())) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot be renamed");
-                }
-
-                // Package declaring static a shared lib cannot declare dynamic libs
-                if (!ArrayUtils.isEmpty(pkg.getLibraryNames())) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot declare dynamic libs");
-                }
-
-                // Package declaring static a shared lib cannot declare shared users
-                if (pkg.getSharedUserId() != null) {
-                    throw new PackageManagerException(
-                            "Packages declaring static-shared libs cannot declare shared users");
-                }
-
-                // Static shared libs cannot declare activities
-                if (!pkg.getActivities().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare activities");
-                }
-
-                // Static shared libs cannot declare services
-                if (!pkg.getServices().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare services");
-                }
-
-                // Static shared libs cannot declare providers
-                if (!pkg.getProviders().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare content providers");
-                }
-
-                // Static shared libs cannot declare receivers
-                if (!pkg.getReceivers().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare broadcast receivers");
-                }
-
-                // Static shared libs cannot declare permission groups
-                if (!pkg.getPermissionGroups().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare permission groups");
-                }
-
-                // Static shared libs cannot declare attributions
-                if (!pkg.getAttributions().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare features");
-                }
-
-                // Static shared libs cannot declare permissions
-                if (!pkg.getPermissions().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare permissions");
-                }
-
-                // Static shared libs cannot declare protected broadcasts
-                if (!pkg.getProtectedBroadcasts().isEmpty()) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot declare protected broadcasts");
-                }
-
-                // Static shared libs cannot be overlay targets
-                if (pkg.getOverlayTarget() != null) {
-                    throw new PackageManagerException(
-                            "Static shared libs cannot be overlay targets");
-                }
-
-                // The version codes must be ordered as lib versions
-                long minVersionCode = Long.MIN_VALUE;
-                long maxVersionCode = Long.MAX_VALUE;
-
-                WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mPm.mSharedLibraries.get(
-                        pkg.getStaticSharedLibName());
-                if (versionedLib != null) {
-                    final int versionCount = versionedLib.size();
-                    for (int i = 0; i < versionCount; i++) {
-                        SharedLibraryInfo libInfo = versionedLib.valueAt(i);
-                        final long libVersionCode = libInfo.getDeclaringPackage()
-                                .getLongVersionCode();
-                        if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
-                            minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
-                        } else if (libInfo.getLongVersion()
-                                > pkg.getStaticSharedLibVersion()) {
-                            maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
-                        } else {
-                            minVersionCode = maxVersionCode = libVersionCode;
-                            break;
-                        }
-                    }
-                }
-                if (pkg.getLongVersionCode() < minVersionCode
-                        || pkg.getLongVersionCode() > maxVersionCode) {
-                    throw new PackageManagerException("Static shared"
-                            + " lib version codes must be ordered as lib versions");
-                }
-            }
-
-            // If we're only installing presumed-existing packages, require that the
-            // scanned APK is both already known and at the path previously established
-            // for it.  Previously unknown packages we pick up normally, but if we have an
-            // a priori expectation about this package's install presence, enforce it.
-            // With a singular exception for new system packages. When an OTA contains
-            // a new system package, we allow the codepath to change from a system location
-            // to the user-installed location. If we don't allow this change, any newer,
-            // user-installed version of the application will be ignored.
-            if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
-                if (mPm.isExpectingBetter(pkg.getPackageName())) {
-                    Slog.w(TAG, "Relax SCAN_REQUIRE_KNOWN requirement for package "
-                            + pkg.getPackageName());
-                } else {
-                    PackageSetting known = mPm.mSettings.getPackageLPr(pkg.getPackageName());
-                    if (known != null) {
-                        if (DEBUG_PACKAGE_SCANNING) {
-                            Log.d(TAG, "Examining " + pkg.getPath()
-                                    + " and requiring known path " + known.getPathString());
-                        }
-                        if (!pkg.getPath().equals(known.getPathString())) {
-                            throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
-                                    "Application package " + pkg.getPackageName()
-                                            + " found at " + pkg.getPath()
-                                            + " but expected at " + known.getPathString()
-                                            + "; ignoring.");
-                        }
-                    } else {
-                        throw new PackageManagerException(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
-                                "Application package " + pkg.getPackageName()
-                                        + " not found; ignoring.");
-                    }
-                }
-            }
-
-            // Verify that this new package doesn't have any content providers
-            // that conflict with existing packages.  Only do this if the
-            // package isn't already installed, since we don't want to break
-            // things that are installed.
-            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
-                mPm.mComponentResolver.assertProvidersNotDefined(pkg);
-            }
-
-            // If this package has defined explicit processes, then ensure that these are
-            // the only processes used by its components.
-            final Map<String, ParsedProcess> procs = pkg.getProcesses();
-            if (!procs.isEmpty()) {
-                if (!procs.containsKey(pkg.getProcessName())) {
-                    throw new PackageManagerException(
-                            INSTALL_FAILED_PROCESS_NOT_DEFINED,
-                            "Can't install because application tag's process attribute "
-                                    + pkg.getProcessName()
-                                    + " (in package " + pkg.getPackageName()
-                                    + ") is not included in the <processes> list");
-                }
-                assertPackageProcesses(pkg, pkg.getActivities(), procs, "activity");
-                assertPackageProcesses(pkg, pkg.getServices(), procs, "service");
-                assertPackageProcesses(pkg, pkg.getReceivers(), procs, "receiver");
-                assertPackageProcesses(pkg, pkg.getProviders(), procs, "provider");
-            }
-
-            // Verify that packages sharing a user with a privileged app are marked as privileged.
-            if (!pkg.isPrivileged() && (pkg.getSharedUserId() != null)) {
-                SharedUserSetting sharedUserSetting = null;
-                try {
-                    sharedUserSetting = mPm.mSettings.getSharedUserLPw(pkg.getSharedUserId(),
-                            0, 0, false);
-                } catch (PackageManagerException ignore) {
-                }
-                if (sharedUserSetting != null && sharedUserSetting.isPrivileged()) {
-                    // Exempt SharedUsers signed with the platform key.
-                    PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
-                    if (!comparePackageSignatures(platformPkgSetting,
-                            pkg.getSigningDetails().getSignatures())) {
-                        throw new PackageManagerException("Apps that share a user with a "
-                                + "privileged app must themselves be marked as privileged. "
-                                + pkg.getPackageName() + " shares privileged user "
-                                + pkg.getSharedUserId() + ".");
-                    }
-                }
-            }
-
-            // Apply policies specific for runtime resource overlays (RROs).
-            if (pkg.getOverlayTarget() != null) {
-                // System overlays have some restrictions on their use of the 'static' state.
-                if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
-                    // We are scanning a system overlay. This can be the first scan of the
-                    // system/vendor/oem partition, or an update to the system overlay.
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                        // This must be an update to a system overlay. Immutable overlays cannot be
-                        // upgraded.
-                        if (!mPm.isOverlayMutable(pkg.getPackageName())) {
-                            throw new PackageManagerException("Overlay "
-                                    + pkg.getPackageName()
-                                    + " is static and cannot be upgraded.");
-                        }
-                    } else {
-                        if ((scanFlags & SCAN_AS_VENDOR) != 0) {
-                            if (pkg.getTargetSdkVersion() < getVendorPartitionVersion()) {
-                                Slog.w(TAG, "System overlay " + pkg.getPackageName()
-                                        + " targets an SDK below the required SDK level of vendor"
-                                        + " overlays (" + getVendorPartitionVersion() + ")."
-                                        + " This will become an install error in a future release");
-                            }
-                        } else if (pkg.getTargetSdkVersion() < Build.VERSION.SDK_INT) {
-                            Slog.w(TAG, "System overlay " + pkg.getPackageName()
-                                    + " targets an SDK below the required SDK level of system"
-                                    + " overlays (" + Build.VERSION.SDK_INT + ")."
-                                    + " This will become an install error in a future release");
-                        }
-                    }
-                } else {
-                    // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
-                    // signed with the platform certificate. Check this in increasing order of
-                    // computational cost.
-                    if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.Q) {
-                        final PackageSetting platformPkgSetting =
-                                mPm.mSettings.getPackageLPr("android");
-                        if (!comparePackageSignatures(platformPkgSetting,
-                                pkg.getSigningDetails().getSignatures())) {
-                            throw new PackageManagerException("Overlay "
-                                    + pkg.getPackageName()
-                                    + " must target Q or later, "
-                                    + "or be signed with the platform certificate");
-                        }
-                    }
-
-                    // A non-preloaded overlay package, without <overlay android:targetName>, will
-                    // only be used if it is signed with the same certificate as its target OR if
-                    // it is signed with the same certificate as a reference package declared
-                    // in 'overlay-config-signature' tag of SystemConfig.
-                    // If the target is already installed or 'overlay-config-signature' tag in
-                    // SystemConfig is set, check this here to augment the last line of defense
-                    // which is OMS.
-                    if (pkg.getOverlayTargetOverlayableName() == null) {
-                        final PackageSetting targetPkgSetting =
-                                mPm.mSettings.getPackageLPr(pkg.getOverlayTarget());
-                        if (targetPkgSetting != null) {
-                            if (!comparePackageSignatures(targetPkgSetting,
-                                    pkg.getSigningDetails().getSignatures())) {
-                                // check reference signature
-                                if (mPm.mOverlayConfigSignaturePackage == null) {
-                                    throw new PackageManagerException("Overlay "
-                                            + pkg.getPackageName() + " and target "
-                                            + pkg.getOverlayTarget() + " signed with"
-                                            + " different certificates, and the overlay lacks"
-                                            + " <overlay android:targetName>");
-                                }
-                                final PackageSetting refPkgSetting =
-                                        mPm.mSettings.getPackageLPr(
-                                                mPm.mOverlayConfigSignaturePackage);
-                                if (!comparePackageSignatures(refPkgSetting,
-                                        pkg.getSigningDetails().getSignatures())) {
-                                    throw new PackageManagerException("Overlay "
-                                            + pkg.getPackageName() + " signed with a different "
-                                            + "certificate than both the reference package and "
-                                            + "target " + pkg.getOverlayTarget() + ", and the "
-                                            + "overlay lacks <overlay android:targetName>");
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-
-            // If the package is not on a system partition ensure it is signed with at least the
-            // minimum signature scheme version required for its target SDK.
-            if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
-                int minSignatureSchemeVersion =
-                        ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
-                                pkg.getTargetSdkVersion());
-                if (pkg.getSigningDetails().getSignatureSchemeVersion()
-                        < minSignatureSchemeVersion) {
-                    throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
-                            "No signature found in package of version " + minSignatureSchemeVersion
-                                    + " or newer for package " + pkg.getPackageName());
-                }
-            }
-        }
-    }
-
-    private static <T extends ParsedMainComponent> void assertPackageProcesses(AndroidPackage pkg,
-            List<T> components, Map<String, ParsedProcess> procs, String compName)
-            throws PackageManagerException {
-        if (components == null) {
-            return;
-        }
-        for (int i = components.size() - 1; i >= 0; i--) {
-            final ParsedMainComponent component = components.get(i);
-            if (!procs.containsKey(component.getProcessName())) {
-                throw new PackageManagerException(
-                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
-                        "Can't install because " + compName + " " + component.getClassName()
-                                + "'s process attribute " + component.getProcessName()
-                                + " (in package " + pkg.getPackageName()
-                                + ") is not included in the <processes> list");
-            }
-        }
-    }
-
-    /**
-     * Applies the adjusted ABI calculated by
-     * {@link PackageAbiHelper#getAdjustedAbiForSharedUser(Set, AndroidPackage)} to all
-     * relevant packages and settings.
-     * @param sharedUserSetting The {@code SharedUserSetting} to adjust
-     * @param scannedPackage the package being scanned or null
-     * @param adjustedAbi the adjusted ABI calculated by {@link PackageAbiHelper}
-     * @return the list of code paths that belong to packages that had their ABIs adjusted.
-     */
-    public static List<String> applyAdjustedAbiToSharedUser(SharedUserSetting sharedUserSetting,
-            ParsedPackage scannedPackage, String adjustedAbi) {
-        if (scannedPackage != null)  {
-            scannedPackage.setPrimaryCpuAbi(adjustedAbi);
-        }
-        List<String> changedAbiCodePath = null;
-        for (PackageSetting ps : sharedUserSetting.packages) {
-            if (scannedPackage == null
-                    || !scannedPackage.getPackageName().equals(ps.getPackageName())) {
-                if (ps.getPrimaryCpuAbi() != null) {
-                    continue;
-                }
-
-                ps.setPrimaryCpuAbi(adjustedAbi);
-                if (ps.getPkg() != null) {
-                    if (!TextUtils.equals(adjustedAbi,
-                            AndroidPackageUtils.getRawPrimaryCpuAbi(ps.getPkg()))) {
-                        if (DEBUG_ABI_SELECTION) {
-                            Slog.i(TAG,
-                                    "Adjusting ABI for " + ps.getPackageName() + " to "
-                                            + adjustedAbi + " (scannedPackage="
-                                            + (scannedPackage != null ? scannedPackage : "null")
-                                            + ")");
-                        }
-                        if (changedAbiCodePath == null) {
-                            changedAbiCodePath = new ArrayList<>();
-                        }
-                        changedAbiCodePath.add(ps.getPathString());
-                    }
-                }
-            }
-        }
-        return changedAbiCodePath;
-    }
-
-    /**
-     * Applies policy to the parsed package based upon the given policy flags.
-     * Ensures the package is in a good state.
-     * <p>
-     * Implementation detail: This method must NOT have any side effect. It would
-     * ideally be static, but, it requires locks to read system state.
-     */
-    private static void applyPolicy(ParsedPackage parsedPackage,
-            final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
-            boolean isUpdatedSystemApp) {
-        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
-            parsedPackage.setSystem(true);
-            // TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
-            //  is set during parse.
-            if (parsedPackage.isDirectBootAware()) {
-                parsedPackage.setAllComponentsDirectBootAware(true);
-            }
-            if (compressedFileExists(parsedPackage.getPath())) {
-                parsedPackage.setStub(true);
-            }
-        } else {
-            parsedPackage
-                    // Non system apps cannot mark any broadcast as protected
-                    .clearProtectedBroadcasts()
-                    // non system apps can't be flagged as core
-                    .setCoreApp(false)
-                    // clear flags not applicable to regular apps
-                    .setPersistent(false)
-                    .setDefaultToDeviceProtectedStorage(false)
-                    .setDirectBootAware(false)
-                    // non system apps can't have permission priority
-                    .capPermissionPriorities();
-        }
-        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
-            parsedPackage
-                    .markNotActivitiesAsNotExportedIfSingleUser();
-        }
-
-        parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0)
-                .setOem((scanFlags & SCAN_AS_OEM) != 0)
-                .setVendor((scanFlags & SCAN_AS_VENDOR) != 0)
-                .setProduct((scanFlags & SCAN_AS_PRODUCT) != 0)
-                .setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0)
-                .setOdm((scanFlags & SCAN_AS_ODM) != 0);
-
-        // Check if the package is signed with the same key as the platform package.
-        parsedPackage.setSignedWithPlatformKey(
-                (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
-                        || (platformPkg != null && compareSignatures(
-                        platformPkg.getSigningDetails().getSignatures(),
-                        parsedPackage.getSigningDetails().getSignatures()
-                ) == PackageManager.SIGNATURE_MATCH))
-        );
-
-        if (!parsedPackage.isSystem()) {
-            // Only system apps can use these features.
-            parsedPackage.clearOriginalPackages()
-                    .clearAdoptPermissions();
-        }
-
-        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
-    }
-
-    /**
-     * Enforces code policy for the package. This ensures that if an APK has
-     * declared hasCode="true" in its manifest that the APK actually contains
-     * code.
-     *
-     * @throws PackageManagerException If bytecode could not be found when it should exist
-     */
-    private static void assertCodePolicy(AndroidPackage pkg)
-            throws PackageManagerException {
-        final boolean shouldHaveCode = pkg.isHasCode();
-        if (shouldHaveCode && !apkHasCode(pkg.getBaseApkPath())) {
-            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                    "Package " + pkg.getBaseApkPath() + " code is missing");
-        }
-
-        if (!ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
-            for (int i = 0; i < pkg.getSplitCodePaths().length; i++) {
-                final boolean splitShouldHaveCode =
-                        (pkg.getSplitFlags()[i] & ApplicationInfo.FLAG_HAS_CODE) != 0;
-                if (splitShouldHaveCode && !apkHasCode(pkg.getSplitCodePaths()[i])) {
-                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
-                            "Package " + pkg.getSplitCodePaths()[i] + " code is missing");
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the "real" name of the package.
-     * <p>This may differ from the package's actual name if the application has already
-     * been installed under one of this package's original names.
-     */
-    private static @Nullable String getRealPackageName(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
-        if (isPackageRenamed(pkg, renamedPkgName)) {
-            return AndroidPackageUtils.getRealPackageOrNull(pkg);
-        }
-        return null;
-    }
-
-    /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
-    private static boolean isPackageRenamed(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
-        return pkg.getOriginalPackages().contains(renamedPkgName);
-    }
-
-    /**
-     * Renames the package if it was installed under a different name.
-     * <p>When we've already installed the package under an original name, update
-     * the new package so we can continue to have the old name.
-     */
-    private static void ensurePackageRenamed(@NonNull ParsedPackage parsedPackage,
-            @NonNull String renamedPackageName) {
-        if (!parsedPackage.getOriginalPackages().contains(renamedPackageName)
-                || parsedPackage.getPackageName().equals(renamedPackageName)) {
-            return;
-        }
-        parsedPackage.setPackageName(renamedPackageName);
-    }
-
-    /**
-     * Returns {@code true} if the given file contains code. Otherwise {@code false}.
-     */
-    private static boolean apkHasCode(String fileName) {
-        StrictJarFile jarFile = null;
-        try {
-            jarFile = new StrictJarFile(fileName,
-                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
-            return jarFile.findEntry("classes.dex") != null;
-        } catch (IOException ignore) {
-        } finally {
-            try {
-                if (jarFile != null) {
-                    jarFile.close();
-                }
-            } catch (IOException ignore) {
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Sets the enabled state of components configured through {@link SystemConfig}.
-     * This modifies the {@link PackageSetting} object.
-     *
-     * TODO(b/135203078): Move this to package parsing
-     **/
-    private static void configurePackageComponents(AndroidPackage pkg) {
-        final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
-                .getComponentsEnabledStates(pkg.getPackageName());
-        if (componentsEnabledStates == null) {
-            return;
-        }
-
-        for (int i = ArrayUtils.size(pkg.getActivities()) - 1; i >= 0; i--) {
-            final ParsedActivity component = pkg.getActivities().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-
-        for (int i = ArrayUtils.size(pkg.getReceivers()) - 1; i >= 0; i--) {
-            final ParsedActivity component = pkg.getReceivers().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-
-        for (int i = ArrayUtils.size(pkg.getProviders()) - 1; i >= 0; i--) {
-            final ParsedProvider component = pkg.getProviders().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-
-        for (int i = ArrayUtils.size(pkg.getServices()) - 1; i >= 0; i--) {
-            final ParsedService component = pkg.getServices().get(i);
-            final Boolean enabled = componentsEnabledStates.get(component.getName());
-            if (enabled != null) {
-                ComponentMutateUtils.setEnabled(component, enabled);
-            }
-        }
-    }
-
-    private static int getVendorPartitionVersion() {
-        final String version = SystemProperties.get("ro.vndk.version");
-        if (!version.isEmpty()) {
-            try {
-                return Integer.parseInt(version);
-            } catch (NumberFormatException ignore) {
-                if (ArrayUtils.contains(Build.VERSION.ACTIVE_CODENAMES, version)) {
-                    return Build.VERSION_CODES.CUR_DEVELOPMENT;
-                }
-            }
-        }
-        return Build.VERSION_CODES.P;
-    }
-}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
new file mode 100644
index 0000000..378c9e0
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -0,0 +1,1006 @@
+/*
+ * 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.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_PROCESS_NOT_DEFINED;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+import static com.android.server.pm.PackageManagerService.DEBUG_ABI_SELECTION;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+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_AS_ODM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_OEM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_PRIVILEGED;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_PRODUCT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_SYSTEM_EXT;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_VENDOR;
+import static com.android.server.pm.PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD;
+import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
+import static com.android.server.pm.PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE;
+import static com.android.server.pm.PackageManagerService.SCAN_MOVE;
+import static com.android.server.pm.PackageManagerService.SCAN_NEW_INSTALL;
+import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
+import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_TIME;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.ParsingPackageUtils;
+import android.content.pm.parsing.component.ComponentMutateUtils;
+import android.content.pm.parsing.component.ParsedActivity;
+import android.content.pm.parsing.component.ParsedMainComponent;
+import android.content.pm.parsing.component.ParsedProcess;
+import android.content.pm.parsing.component.ParsedProvider;
+import android.content.pm.parsing.component.ParsedService;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Process;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.jar.StrictJarFile;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
+
+import dalvik.system.VMRuntime;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Helper class that handles package scanning logic
+ */
+final class ScanPackageUtils {
+    /**
+     * Just scans the package without any side effects.
+     *
+     * @param injector injector for acquiring dependencies
+     * @param request Information about the package to be scanned
+     * @param isUnderFactoryTest Whether or not the device is under factory test
+     * @param currentTime The current time, in millis
+     * @return The results of the scan
+     */
+    @GuardedBy("mPm.mInstallLock")
+    @VisibleForTesting
+    @NonNull
+    public static ScanResult scanPackageOnlyLI(@NonNull ScanRequest request,
+            PackageManagerServiceInjector injector,
+            boolean isUnderFactoryTest, long currentTime)
+            throws PackageManagerException {
+        final PackageAbiHelper packageAbiHelper = injector.getAbiHelper();
+        ParsedPackage parsedPackage = request.mParsedPackage;
+        PackageSetting pkgSetting = request.mPkgSetting;
+        final PackageSetting disabledPkgSetting = request.mDisabledPkgSetting;
+        final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
+        final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
+        final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
+        final String realPkgName = request.mRealPkgName;
+        final SharedUserSetting sharedUserSetting = request.mSharedUserSetting;
+        final UserHandle user = request.mUser;
+        final boolean isPlatformPackage = request.mIsPlatformPackage;
+
+        List<String> changedAbiCodePath = null;
+
+        if (DEBUG_PACKAGE_SCANNING) {
+            if ((parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0) {
+                Log.d(TAG, "Scanning package " + parsedPackage.getPackageName());
+            }
+        }
+
+        // Initialize package source and resource directories
+        final File destCodeFile = new File(parsedPackage.getPath());
+
+        // We keep references to the derived CPU Abis from settings in oder to reuse
+        // them in the case where we're not upgrading or booting for the first time.
+        String primaryCpuAbiFromSettings = null;
+        String secondaryCpuAbiFromSettings = null;
+        boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+        if (!needToDeriveAbi) {
+            if (pkgSetting != null) {
+                // TODO(b/154610922): if it is not first boot or upgrade, we should directly use
+                // API info from existing package setting. However, stub packages currently do not
+                // preserve ABI info, thus the special condition check here. Remove the special
+                // check after we fix the stub generation.
+                if (pkgSetting.getPkg() != null && pkgSetting.getPkg().isStub()) {
+                    needToDeriveAbi = true;
+                } else {
+                    primaryCpuAbiFromSettings = pkgSetting.getPrimaryCpuAbi();
+                    secondaryCpuAbiFromSettings = pkgSetting.getSecondaryCpuAbi();
+                }
+            } else {
+                // Re-scanning a system package after uninstalling updates; need to derive ABI
+                needToDeriveAbi = true;
+            }
+        }
+
+        int previousAppId = Process.INVALID_UID;
+
+        if (pkgSetting != null && pkgSetting.getSharedUser() != sharedUserSetting) {
+            if (pkgSetting.getSharedUser() != null && sharedUserSetting == null) {
+                previousAppId = pkgSetting.getAppId();
+                // Log that something is leaving shareduid and keep going
+                Slog.i(TAG,
+                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
+                                + pkgSetting.getSharedUser().name + " to " + "<nothing>.");
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Package " + parsedPackage.getPackageName() + " shared user changed from "
+                                + (pkgSetting.getSharedUser() != null
+                                ? pkgSetting.getSharedUser().name : "<nothing>")
+                                + " to "
+                                + (sharedUserSetting != null ? sharedUserSetting.name : "<nothing>")
+                                + "; replacing with new");
+                pkgSetting = null;
+            }
+        }
+
+        String[] usesSdkLibraries = null;
+        if (!parsedPackage.getUsesSdkLibraries().isEmpty()) {
+            usesSdkLibraries = new String[parsedPackage.getUsesSdkLibraries().size()];
+            parsedPackage.getUsesSdkLibraries().toArray(usesSdkLibraries);
+        }
+
+        String[] usesStaticLibraries = null;
+        if (!parsedPackage.getUsesStaticLibraries().isEmpty()) {
+            usesStaticLibraries = new String[parsedPackage.getUsesStaticLibraries().size()];
+            parsedPackage.getUsesStaticLibraries().toArray(usesStaticLibraries);
+        }
+
+        final UUID newDomainSetId = injector.getDomainVerificationManagerInternal().generateNewId();
+
+        // TODO(b/135203078): Remove appInfoFlag usage in favor of individually assigned booleans
+        //  to avoid adding something that's unsupported due to lack of state, since it's called
+        //  with null.
+        final boolean createNewPackage = (pkgSetting == null);
+        if (createNewPackage) {
+            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
+            final boolean virtualPreload = (scanFlags & SCAN_AS_VIRTUAL_PRELOAD) != 0;
+
+            // Flags contain system values stored in the server variant of AndroidPackage,
+            // and so the server-side PackageInfoUtils is still called, even without a
+            // PackageSetting to pass in.
+            int pkgFlags = PackageInfoUtils.appInfoFlags(parsedPackage, null);
+            int pkgPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(parsedPackage, null);
+
+            // REMOVE SharedUserSetting from method; update in a separate call
+            pkgSetting = Settings.createNewSetting(parsedPackage.getPackageName(),
+                    originalPkgSetting, disabledPkgSetting, realPkgName, sharedUserSetting,
+                    destCodeFile, parsedPackage.getNativeLibraryRootDir(),
+                    AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage),
+                    AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage),
+                    parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user,
+                    true /*allowInstall*/, instantApp, virtualPreload,
+                    UserManagerService.getInstance(), usesSdkLibraries,
+                    parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
+                    parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
+                    newDomainSetId);
+        } else {
+            // make a deep copy to avoid modifying any existing system state.
+            pkgSetting = new PackageSetting(pkgSetting);
+            pkgSetting.setPkg(parsedPackage);
+
+            // REMOVE SharedUserSetting from method; update in a separate call.
+            //
+            // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi,
+            // secondaryCpuAbi are not known at this point so we always update them
+            // to null here, only to reset them at a later point.
+            Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, sharedUserSetting,
+                    destCodeFile, parsedPackage.getNativeLibraryDir(),
+                    AndroidPackageUtils.getPrimaryCpuAbi(parsedPackage, pkgSetting),
+                    AndroidPackageUtils.getSecondaryCpuAbi(parsedPackage, pkgSetting),
+                    PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
+                    PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting),
+                    UserManagerService.getInstance(),
+                    usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
+                    usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
+                    parsedPackage.getMimeGroups(), newDomainSetId);
+        }
+        if (createNewPackage && originalPkgSetting != null) {
+            // This is the initial transition from the original package, so,
+            // fix up the new package's name now. We must do this after looking
+            // up the package under its new name, so getPackageLP takes care of
+            // fiddling things correctly.
+            parsedPackage.setPackageName(originalPkgSetting.getPackageName());
+
+            // File a report about this.
+            String msg = "New package " + pkgSetting.getRealName()
+                    + " renamed to replace old package " + pkgSetting.getPackageName();
+            PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+        }
+
+        final int userId = (user == null ? UserHandle.USER_SYSTEM : user.getIdentifier());
+        // for existing packages, change the install state; but, only if it's explicitly specified
+        if (!createNewPackage) {
+            final boolean instantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
+            final boolean fullApp = (scanFlags & SCAN_AS_FULL_APP) != 0;
+            setInstantAppForUser(injector, pkgSetting, userId, instantApp, fullApp);
+        }
+        // TODO(patb): see if we can do away with disabled check here.
+        if (disabledPkgSetting != null
+                || (0 != (scanFlags & SCAN_NEW_INSTALL)
+                && pkgSetting != null && pkgSetting.isSystem())) {
+            pkgSetting.getPkgState().setUpdatedSystemApp(true);
+        }
+
+        parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
+                injector.getCompatibility()));
+
+        if (parsedPackage.isSystem()) {
+            configurePackageComponents(parsedPackage);
+        }
+
+        final String cpuAbiOverride = deriveAbiOverride(request.mCpuAbiOverride);
+        final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
+
+        final File appLib32InstallDir = getAppLib32InstallDir();
+        if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
+            if (needToDeriveAbi) {
+                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
+                final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
+                        packageAbiHelper.derivePackageAbi(parsedPackage, isUpdatedSystemApp,
+                                cpuAbiOverride, appLib32InstallDir);
+                derivedAbi.first.applyTo(parsedPackage);
+                derivedAbi.second.applyTo(parsedPackage);
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+
+                // Some system apps still use directory structure for native libraries
+                // in which case we might end up not detecting abi solely based on apk
+                // structure. Try to detect abi based on directory structure.
+
+                String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
+                if (parsedPackage.isSystem() && !isUpdatedSystemApp
+                        && pkgRawPrimaryCpuAbi == null) {
+                    final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
+                            parsedPackage);
+                    abis.applyTo(parsedPackage);
+                    abis.applyTo(pkgSetting);
+                    final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                                    isUpdatedSystemApp, appLib32InstallDir);
+                    nativeLibraryPaths.applyTo(parsedPackage);
+                }
+            } else {
+                // This is not a first boot or an upgrade, don't bother deriving the
+                // ABI during the scan. Instead, trust the value that was stored in the
+                // package setting.
+                parsedPackage.setPrimaryCpuAbi(primaryCpuAbiFromSettings)
+                        .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
+
+                final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                                isUpdatedSystemApp, appLib32InstallDir);
+                nativeLibraryPaths.applyTo(parsedPackage);
+
+                if (DEBUG_ABI_SELECTION) {
+                    Slog.i(TAG, "Using ABIS and native lib paths from settings : "
+                            + parsedPackage.getPackageName() + " "
+                            + AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage)
+                            + ", "
+                            + AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage));
+                }
+            }
+        } else {
+            if ((scanFlags & SCAN_MOVE) != 0) {
+                // We haven't run dex-opt for this move (since we've moved the compiled output too)
+                // but we already have this packages package info in the PackageSetting. We just
+                // use that and derive the native library path based on the new code path.
+                parsedPackage.setPrimaryCpuAbi(pkgSetting.getPrimaryCpuAbi())
+                        .setSecondaryCpuAbi(pkgSetting.getSecondaryCpuAbi());
+            }
+
+            // Set native library paths again. For moves, the path will be updated based on the
+            // ABIs we've determined above. For non-moves, the path will be updated based on the
+            // ABIs we determined during compilation, but the path will depend on the final
+            // package path (after the rename away from the stage path).
+            final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
+                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isUpdatedSystemApp,
+                            appLib32InstallDir);
+            nativeLibraryPaths.applyTo(parsedPackage);
+        }
+
+        // This is a special case for the "system" package, where the ABI is
+        // dictated by the zygote configuration (and init.rc). We should keep track
+        // of this ABI so that we can deal with "normal" applications that run under
+        // the same UID correctly.
+        if (isPlatformPackage) {
+            parsedPackage.setPrimaryCpuAbi(VMRuntime.getRuntime().is64Bit()
+                    ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0]);
+        }
+
+        // If there's a mismatch between the abi-override in the package setting
+        // and the abiOverride specified for the install. Warn about this because we
+        // would've already compiled the app without taking the package setting into
+        // account.
+        if ((scanFlags & SCAN_NO_DEX) == 0 && (scanFlags & SCAN_NEW_INSTALL) != 0) {
+            if (cpuAbiOverride == null) {
+                Slog.w(TAG, "Ignoring persisted ABI override for package "
+                        + parsedPackage.getPackageName());
+            }
+        }
+
+        pkgSetting.setPrimaryCpuAbi(AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage))
+                .setSecondaryCpuAbi(AndroidPackageUtils.getRawSecondaryCpuAbi(parsedPackage))
+                .setCpuAbiOverride(cpuAbiOverride);
+
+        if (DEBUG_ABI_SELECTION) {
+            Slog.d(TAG, "Resolved nativeLibraryRoot for " + parsedPackage.getPackageName()
+                    + " to root=" + parsedPackage.getNativeLibraryRootDir()
+                    + ", to dir=" + parsedPackage.getNativeLibraryDir()
+                    + ", isa=" + parsedPackage.isNativeLibraryRootRequiresIsa());
+        }
+
+        // Push the derived path down into PackageSettings so we know what to
+        // clean up at uninstall time.
+        pkgSetting.setLegacyNativeLibraryPath(parsedPackage.getNativeLibraryRootDir());
+
+        if (DEBUG_ABI_SELECTION) {
+            Log.d(TAG, "Abis for package[" + parsedPackage.getPackageName() + "] are"
+                    + " primary=" + pkgSetting.getPrimaryCpuAbi()
+                    + " secondary=" + pkgSetting.getSecondaryCpuAbi()
+                    + " abiOverride=" + pkgSetting.getCpuAbiOverride());
+        }
+
+        if ((scanFlags & SCAN_BOOTING) == 0 && pkgSetting.getSharedUser() != null) {
+            // We don't do this here during boot because we can do it all
+            // at once after scanning all existing packages.
+            //
+            // We also do this *before* we perform dexopt on this package, so that
+            // we can avoid redundant dexopts, and also to make sure we've got the
+            // code and package path correct.
+            changedAbiCodePath = applyAdjustedAbiToSharedUser(pkgSetting.getSharedUser(),
+                    parsedPackage, packageAbiHelper.getAdjustedAbiForSharedUser(
+                            pkgSetting.getSharedUser().packages, parsedPackage));
+        }
+
+        parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions()
+                .contains(android.Manifest.permission.FACTORY_TEST));
+
+        if (parsedPackage.isSystem()) {
+            pkgSetting.setIsOrphaned(true);
+        }
+
+        // Take care of first install / last update times.
+        final long scanFileTime = getLastModifiedTime(parsedPackage);
+        if (currentTime != 0) {
+            if (pkgSetting.getFirstInstallTime() == 0) {
+                pkgSetting.setFirstInstallTime(currentTime)
+                        .setLastUpdateTime(currentTime);
+            } else if ((scanFlags & SCAN_UPDATE_TIME) != 0) {
+                pkgSetting.setLastUpdateTime(currentTime);
+            }
+        } else if (pkgSetting.getFirstInstallTime() == 0) {
+            // We need *something*.  Take time time stamp of the file.
+            pkgSetting.setFirstInstallTime(scanFileTime)
+                    .setLastUpdateTime(scanFileTime);
+        } else if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0) {
+            if (scanFileTime != pkgSetting.getLastModifiedTime()) {
+                // A package on the system image has changed; consider this
+                // to be an update.
+                pkgSetting.setLastUpdateTime(scanFileTime);
+            }
+        }
+        pkgSetting.setLastModifiedTime(scanFileTime);
+        // TODO(b/135203078): Remove, move to constructor
+        pkgSetting.setPkg(parsedPackage)
+                .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
+                .setPrivateFlags(
+                        PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
+        if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
+            pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
+        }
+        // Update volume if needed
+        final String volumeUuid = parsedPackage.getVolumeUuid();
+        if (!Objects.equals(volumeUuid, pkgSetting.getVolumeUuid())) {
+            Slog.i(PackageManagerService.TAG,
+                    "Update" + (pkgSetting.isSystem() ? " system" : "")
+                            + " package " + parsedPackage.getPackageName()
+                            + " volume from " + pkgSetting.getVolumeUuid()
+                            + " to " + volumeUuid);
+            pkgSetting.setVolumeUuid(volumeUuid);
+        }
+
+        SharedLibraryInfo sdkLibraryInfo = null;
+        if (!TextUtils.isEmpty(parsedPackage.getSdkLibName())) {
+            sdkLibraryInfo = AndroidPackageUtils.createSharedLibraryForSdk(parsedPackage);
+        }
+        SharedLibraryInfo staticSharedLibraryInfo = null;
+        if (!TextUtils.isEmpty(parsedPackage.getStaticSharedLibName())) {
+            staticSharedLibraryInfo =
+                    AndroidPackageUtils.createSharedLibraryForStatic(parsedPackage);
+        }
+        List<SharedLibraryInfo> dynamicSharedLibraryInfos = null;
+        if (!ArrayUtils.isEmpty(parsedPackage.getLibraryNames())) {
+            dynamicSharedLibraryInfos = new ArrayList<>(parsedPackage.getLibraryNames().size());
+            for (String name : parsedPackage.getLibraryNames()) {
+                dynamicSharedLibraryInfos.add(
+                        AndroidPackageUtils.createSharedLibraryForDynamic(parsedPackage, name));
+            }
+        }
+
+        return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
+                !createNewPackage /* existingSettingCopied */,
+                previousAppId, sdkLibraryInfo, staticSharedLibraryInfo,
+                dynamicSharedLibraryInfos);
+    }
+
+    /**
+     * Returns the actual scan flags depending upon the state of the other settings.
+     * <p>Updated system applications will not have the following flags set
+     * by default and need to be adjusted after the fact:
+     * <ul>
+     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_PRIVILEGED}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_OEM}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_VENDOR}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_PRODUCT}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_SYSTEM_EXT}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_INSTANT_APP}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_VIRTUAL_PRELOAD}</li>
+     * <li>{@link PackageManagerService.SCAN_AS_ODM}</li>
+     * </ul>
+     */
+    public static @PackageManagerService.ScanFlags int adjustScanFlagsWithPackageSetting(
+            @PackageManagerService.ScanFlags int scanFlags,
+            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user) {
+
+        // TODO(patb): Do away entirely with disabledPkgSetting here. PkgSetting will always contain
+        // the correct isSystem value now that we don't disable system packages before scan.
+        final PackageSetting systemPkgSetting =
+                (scanFlags & SCAN_NEW_INSTALL) != 0 && disabledPkgSetting == null
+                        && pkgSetting != null && pkgSetting.isSystem()
+                        ? pkgSetting
+                        : disabledPkgSetting;
+        if (systemPkgSetting != null)  {
+            // updated system application, must at least have SCAN_AS_SYSTEM
+            scanFlags |= SCAN_AS_SYSTEM;
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                scanFlags |= SCAN_AS_PRIVILEGED;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+                scanFlags |= SCAN_AS_OEM;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0) {
+                scanFlags |= SCAN_AS_VENDOR;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0) {
+                scanFlags |= SCAN_AS_PRODUCT;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
+                scanFlags |= SCAN_AS_SYSTEM_EXT;
+            }
+            if ((systemPkgSetting.getPrivateFlags()
+                    & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+                scanFlags |= SCAN_AS_ODM;
+            }
+        }
+        if (pkgSetting != null) {
+            final int userId = ((user == null) ? 0 : user.getIdentifier());
+            if (pkgSetting.getInstantApp(userId)) {
+                scanFlags |= SCAN_AS_INSTANT_APP;
+            }
+            if (pkgSetting.getVirtualPreload(userId)) {
+                scanFlags |= SCAN_AS_VIRTUAL_PRELOAD;
+            }
+        }
+
+        return scanFlags;
+    }
+
+    /**
+     * Enforces code policy for the package. This ensures that if an APK has
+     * declared hasCode="true" in its manifest that the APK actually contains
+     * code.
+     *
+     * @throws PackageManagerException If bytecode could not be found when it should exist
+     */
+    public static void assertCodePolicy(AndroidPackage pkg)
+            throws PackageManagerException {
+        final boolean shouldHaveCode = pkg.isHasCode();
+        if (shouldHaveCode && !apkHasCode(pkg.getBaseApkPath())) {
+            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                    "Package " + pkg.getBaseApkPath() + " code is missing");
+        }
+
+        if (!ArrayUtils.isEmpty(pkg.getSplitCodePaths())) {
+            for (int i = 0; i < pkg.getSplitCodePaths().length; i++) {
+                final boolean splitShouldHaveCode =
+                        (pkg.getSplitFlags()[i] & ApplicationInfo.FLAG_HAS_CODE) != 0;
+                if (splitShouldHaveCode && !apkHasCode(pkg.getSplitCodePaths()[i])) {
+                    throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+                            "Package " + pkg.getSplitCodePaths()[i] + " code is missing");
+                }
+            }
+        }
+    }
+
+    public static void assertStaticSharedLibraryIsValid(AndroidPackage pkg,
+            @PackageManagerService.ScanFlags int scanFlags) throws PackageManagerException {
+        // Static shared libraries should have at least O target SDK
+        if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.O) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs must target O SDK or higher");
+        }
+
+        // Package declaring static a shared lib cannot be instant apps
+        if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot be instant apps");
+        }
+
+        // Package declaring static a shared lib cannot be renamed since the package
+        // name is synthetic and apps can't code around package manager internals.
+        if (!ArrayUtils.isEmpty(pkg.getOriginalPackages())) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot be renamed");
+        }
+
+        // Package declaring static a shared lib cannot declare dynamic libs
+        if (!ArrayUtils.isEmpty(pkg.getLibraryNames())) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot declare dynamic libs");
+        }
+
+        // Package declaring static a shared lib cannot declare shared users
+        if (pkg.getSharedUserId() != null) {
+            throw new PackageManagerException(
+                    "Packages declaring static-shared libs cannot declare shared users");
+        }
+
+        // Static shared libs cannot declare activities
+        if (!pkg.getActivities().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare activities");
+        }
+
+        // Static shared libs cannot declare services
+        if (!pkg.getServices().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare services");
+        }
+
+        // Static shared libs cannot declare providers
+        if (!pkg.getProviders().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare content providers");
+        }
+
+        // Static shared libs cannot declare receivers
+        if (!pkg.getReceivers().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare broadcast receivers");
+        }
+
+        // Static shared libs cannot declare permission groups
+        if (!pkg.getPermissionGroups().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare permission groups");
+        }
+
+        // Static shared libs cannot declare attributions
+        if (!pkg.getAttributions().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare features");
+        }
+
+        // Static shared libs cannot declare permissions
+        if (!pkg.getPermissions().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare permissions");
+        }
+
+        // Static shared libs cannot declare protected broadcasts
+        if (!pkg.getProtectedBroadcasts().isEmpty()) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot declare protected broadcasts");
+        }
+
+        // Static shared libs cannot be overlay targets
+        if (pkg.getOverlayTarget() != null) {
+            throw new PackageManagerException(
+                    "Static shared libs cannot be overlay targets");
+        }
+    }
+
+    public static void assertProcessesAreValid(AndroidPackage pkg) throws PackageManagerException {
+        final Map<String, ParsedProcess> procs = pkg.getProcesses();
+        if (!procs.isEmpty()) {
+            if (!procs.containsKey(pkg.getProcessName())) {
+                throw new PackageManagerException(
+                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
+                        "Can't install because application tag's process attribute "
+                                + pkg.getProcessName()
+                                + " (in package " + pkg.getPackageName()
+                                + ") is not included in the <processes> list");
+            }
+            assertPackageProcesses(pkg, pkg.getActivities(), procs, "activity");
+            assertPackageProcesses(pkg, pkg.getServices(), procs, "service");
+            assertPackageProcesses(pkg, pkg.getReceivers(), procs, "receiver");
+            assertPackageProcesses(pkg, pkg.getProviders(), procs, "provider");
+        }
+    }
+
+    private static <T extends ParsedMainComponent> void assertPackageProcesses(AndroidPackage pkg,
+            List<T> components, Map<String, ParsedProcess> procs, String compName)
+            throws PackageManagerException {
+        if (components == null) {
+            return;
+        }
+        for (int i = components.size() - 1; i >= 0; i--) {
+            final ParsedMainComponent component = components.get(i);
+            if (!procs.containsKey(component.getProcessName())) {
+                throw new PackageManagerException(
+                        INSTALL_FAILED_PROCESS_NOT_DEFINED,
+                        "Can't install because " + compName + " " + component.getClassName()
+                                + "'s process attribute " + component.getProcessName()
+                                + " (in package " + pkg.getPackageName()
+                                + ") is not included in the <processes> list");
+            }
+        }
+    }
+
+    public static void assertMinSignatureSchemeIsValid(AndroidPackage pkg,
+            @ParsingPackageUtils.ParseFlags int parseFlags) throws PackageManagerException {
+        if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+            int minSignatureSchemeVersion =
+                    ApkSignatureVerifier.getMinimumSignatureSchemeVersionForTargetSdk(
+                            pkg.getTargetSdkVersion());
+            if (pkg.getSigningDetails().getSignatureSchemeVersion()
+                    < minSignatureSchemeVersion) {
+                throw new PackageManagerException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
+                        "No signature found in package of version " + minSignatureSchemeVersion
+                                + " or newer for package " + pkg.getPackageName());
+            }
+        }
+    }
+
+    /**
+     * Returns the "real" name of the package.
+     * <p>This may differ from the package's actual name if the application has already
+     * been installed under one of this package's original names.
+     */
+    public static @Nullable String getRealPackageName(@NonNull AndroidPackage pkg,
+            @Nullable String renamedPkgName) {
+        if (isPackageRenamed(pkg, renamedPkgName)) {
+            return AndroidPackageUtils.getRealPackageOrNull(pkg);
+        }
+        return null;
+    }
+
+    /** Returns {@code true} if the package has been renamed. Otherwise, {@code false}. */
+    public static boolean isPackageRenamed(@NonNull AndroidPackage pkg,
+            @Nullable String renamedPkgName) {
+        return pkg.getOriginalPackages().contains(renamedPkgName);
+    }
+
+    /**
+     * Renames the package if it was installed under a different name.
+     * <p>When we've already installed the package under an original name, update
+     * the new package so we can continue to have the old name.
+     */
+    public static void ensurePackageRenamed(@NonNull ParsedPackage parsedPackage,
+            @NonNull String renamedPackageName) {
+        if (!parsedPackage.getOriginalPackages().contains(renamedPackageName)
+                || parsedPackage.getPackageName().equals(renamedPackageName)) {
+            return;
+        }
+        parsedPackage.setPackageName(renamedPackageName);
+    }
+
+    /**
+     * Returns {@code true} if the given file contains code. Otherwise {@code false}.
+     */
+    public static boolean apkHasCode(String fileName) {
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(fileName,
+                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+            return jarFile.findEntry("classes.dex") != null;
+        } catch (IOException ignore) {
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sets the enabled state of components configured through {@link SystemConfig}.
+     * This modifies the {@link PackageSetting} object.
+     *
+     * TODO(b/135203078): Move this to package parsing
+     **/
+    public static void configurePackageComponents(AndroidPackage pkg) {
+        final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
+                .getComponentsEnabledStates(pkg.getPackageName());
+        if (componentsEnabledStates == null) {
+            return;
+        }
+
+        for (int i = ArrayUtils.size(pkg.getActivities()) - 1; i >= 0; i--) {
+            final ParsedActivity component = pkg.getActivities().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+
+        for (int i = ArrayUtils.size(pkg.getReceivers()) - 1; i >= 0; i--) {
+            final ParsedActivity component = pkg.getReceivers().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+
+        for (int i = ArrayUtils.size(pkg.getProviders()) - 1; i >= 0; i--) {
+            final ParsedProvider component = pkg.getProviders().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+
+        for (int i = ArrayUtils.size(pkg.getServices()) - 1; i >= 0; i--) {
+            final ParsedService component = pkg.getServices().get(i);
+            final Boolean enabled = componentsEnabledStates.get(component.getName());
+            if (enabled != null) {
+                ComponentMutateUtils.setEnabled(component, enabled);
+            }
+        }
+    }
+
+    public static int getVendorPartitionVersion() {
+        final String version = SystemProperties.get("ro.vndk.version");
+        if (!version.isEmpty()) {
+            try {
+                return Integer.parseInt(version);
+            } catch (NumberFormatException ignore) {
+                if (ArrayUtils.contains(Build.VERSION.ACTIVE_CODENAMES, version)) {
+                    return Build.VERSION_CODES.CUR_DEVELOPMENT;
+                }
+            }
+        }
+        return Build.VERSION_CODES.P;
+    }
+
+    /**
+     * Applies policy to the parsed package based upon the given policy flags.
+     * Ensures the package is in a good state.
+     * <p>
+     * Implementation detail: This method must NOT have any side effect. It would
+     * ideally be static, but, it requires locks to read system state.
+     */
+    public static void applyPolicy(ParsedPackage parsedPackage,
+            final @PackageManagerService.ScanFlags int scanFlags, AndroidPackage platformPkg,
+            boolean isUpdatedSystemApp) {
+        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+            parsedPackage.setSystem(true);
+            // TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
+            //  is set during parse.
+            if (parsedPackage.isDirectBootAware()) {
+                parsedPackage.setAllComponentsDirectBootAware(true);
+            }
+            if (compressedFileExists(parsedPackage.getPath())) {
+                parsedPackage.setStub(true);
+            }
+        } else {
+            parsedPackage
+                    // Non system apps cannot mark any broadcast as protected
+                    .clearProtectedBroadcasts()
+                    // non system apps can't be flagged as core
+                    .setCoreApp(false)
+                    // clear flags not applicable to regular apps
+                    .setPersistent(false)
+                    .setDefaultToDeviceProtectedStorage(false)
+                    .setDirectBootAware(false)
+                    // non system apps can't have permission priority
+                    .capPermissionPriorities();
+        }
+        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
+            parsedPackage
+                    .markNotActivitiesAsNotExportedIfSingleUser();
+        }
+
+        parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0)
+                .setOem((scanFlags & SCAN_AS_OEM) != 0)
+                .setVendor((scanFlags & SCAN_AS_VENDOR) != 0)
+                .setProduct((scanFlags & SCAN_AS_PRODUCT) != 0)
+                .setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0)
+                .setOdm((scanFlags & SCAN_AS_ODM) != 0);
+
+        // Check if the package is signed with the same key as the platform package.
+        parsedPackage.setSignedWithPlatformKey(
+                (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
+                        || (platformPkg != null && compareSignatures(
+                        platformPkg.getSigningDetails().getSignatures(),
+                        parsedPackage.getSigningDetails().getSignatures()
+                ) == PackageManager.SIGNATURE_MATCH))
+        );
+
+        if (!parsedPackage.isSystem()) {
+            // Only system apps can use these features.
+            parsedPackage.clearOriginalPackages()
+                    .clearAdoptPermissions();
+        }
+
+        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
+    }
+
+    /**
+     * Applies the adjusted ABI calculated by
+     * {@link PackageAbiHelper#getAdjustedAbiForSharedUser(Set, AndroidPackage)} to all
+     * relevant packages and settings.
+     * @param sharedUserSetting The {@code SharedUserSetting} to adjust
+     * @param scannedPackage the package being scanned or null
+     * @param adjustedAbi the adjusted ABI calculated by {@link PackageAbiHelper}
+     * @return the list of code paths that belong to packages that had their ABIs adjusted.
+     */
+    public static List<String> applyAdjustedAbiToSharedUser(SharedUserSetting sharedUserSetting,
+            ParsedPackage scannedPackage, String adjustedAbi) {
+        if (scannedPackage != null)  {
+            scannedPackage.setPrimaryCpuAbi(adjustedAbi);
+        }
+        List<String> changedAbiCodePath = null;
+        for (PackageSetting ps : sharedUserSetting.packages) {
+            if (scannedPackage == null
+                    || !scannedPackage.getPackageName().equals(ps.getPackageName())) {
+                if (ps.getPrimaryCpuAbi() != null) {
+                    continue;
+                }
+
+                ps.setPrimaryCpuAbi(adjustedAbi);
+                if (ps.getPkg() != null) {
+                    if (!TextUtils.equals(adjustedAbi,
+                            AndroidPackageUtils.getRawPrimaryCpuAbi(ps.getPkg()))) {
+                        if (DEBUG_ABI_SELECTION) {
+                            Slog.i(TAG,
+                                    "Adjusting ABI for " + ps.getPackageName() + " to "
+                                            + adjustedAbi + " (scannedPackage="
+                                            + (scannedPackage != null ? scannedPackage : "null")
+                                            + ")");
+                        }
+                        if (changedAbiCodePath == null) {
+                            changedAbiCodePath = new ArrayList<>();
+                        }
+                        changedAbiCodePath.add(ps.getPathString());
+                    }
+                }
+            }
+        }
+        return changedAbiCodePath;
+    }
+
+    public static void collectCertificatesLI(PackageSetting ps, ParsedPackage parsedPackage,
+            Settings.VersionInfo settingsVersionForPackage, boolean forceCollect,
+            boolean skipVerify, boolean isPreNMR1Upgrade)
+            throws PackageManagerException {
+        // When upgrading from pre-N MR1, verify the package time stamp using the package
+        // directory and not the APK file.
+        final long lastModifiedTime = isPreNMR1Upgrade
+                ? new File(parsedPackage.getPath()).lastModified()
+                : getLastModifiedTime(parsedPackage);
+        if (ps != null && !forceCollect
+                && ps.getPathString().equals(parsedPackage.getPath())
+                && ps.getLastModifiedTime() == lastModifiedTime
+                && !ReconcilePackageUtils.isCompatSignatureUpdateNeeded(settingsVersionForPackage)
+                && !ReconcilePackageUtils.isRecoverSignatureUpdateNeeded(
+                settingsVersionForPackage)) {
+            if (ps.getSigningDetails().getSignatures() != null
+                    && ps.getSigningDetails().getSignatures().length != 0
+                    && ps.getSigningDetails().getSignatureSchemeVersion()
+                    != SigningDetails.SignatureSchemeVersion.UNKNOWN) {
+                // Optimization: reuse the existing cached signing data
+                // if the package appears to be unchanged.
+                parsedPackage.setSigningDetails(
+                        new SigningDetails(ps.getSigningDetails()));
+                return;
+            }
+
+            Slog.w(TAG, "PackageSetting for " + ps.getPackageName()
+                    + " is missing signatures.  Collecting certs again to recover them.");
+        } else {
+            Slog.i(TAG, parsedPackage.getPath() + " changed; collecting certs"
+                    + (forceCollect ? " (forced)" : ""));
+        }
+
+        try {
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
+            final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+            final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
+                    input, parsedPackage, skipVerify);
+            if (result.isError()) {
+                throw new PackageManagerException(
+                        result.getErrorCode(), result.getErrorMessage(), result.getException());
+            }
+            parsedPackage.setSigningDetails(result.getResult());
+        } finally {
+            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+        }
+    }
+
+    public static void setInstantAppForUser(PackageManagerServiceInjector injector,
+            PackageSetting pkgSetting, int userId, boolean instantApp, boolean fullApp) {
+        // no state specified; do nothing
+        if (!instantApp && !fullApp) {
+            return;
+        }
+        if (userId != UserHandle.USER_ALL) {
+            if (instantApp && !pkgSetting.getInstantApp(userId)) {
+                pkgSetting.setInstantApp(true /*instantApp*/, userId);
+            } else if (fullApp && pkgSetting.getInstantApp(userId)) {
+                pkgSetting.setInstantApp(false /*instantApp*/, userId);
+            }
+        } else {
+            for (int currentUserId : injector.getUserManagerInternal().getUserIds()) {
+                if (instantApp && !pkgSetting.getInstantApp(currentUserId)) {
+                    pkgSetting.setInstantApp(true /*instantApp*/, currentUserId);
+                } else if (fullApp && pkgSetting.getInstantApp(currentUserId)) {
+                    pkgSetting.setInstantApp(false /*instantApp*/, currentUserId);
+                }
+            }
+        }
+    }
+
+    /** Directory where installed application's 32-bit native libraries are copied. */
+    public static File getAppLib32InstallDir() {
+        return new File(Environment.getDataDirectory(), "app-lib");
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 45994f6..b1ce6a2 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1254,11 +1254,14 @@
         }
     }
 
-    private void checkAndPruneSharedUserLPw(SharedUserSetting s, boolean skipCheck) {
+    boolean checkAndPruneSharedUserLPw(SharedUserSetting s, boolean skipCheck) {
         if (skipCheck || (s.packages.isEmpty() && s.mDisabledPackages.isEmpty())) {
-            mSharedUsers.remove(s.name);
-            removeAppIdLPw(s.userId);
+            if (mSharedUsers.remove(s.name) != null) {
+                removeAppIdLPw(s.userId);
+                return true;
+            }
         }
+        return false;
     }
 
     int removePackageLPw(String name) {
@@ -1267,7 +1270,9 @@
             removeInstallerPackageStatus(name);
             if (p.getSharedUser() != null) {
                 p.getSharedUser().removePackage(p);
-                checkAndPruneSharedUserLPw(p.getSharedUser(), false);
+                if (checkAndPruneSharedUserLPw(p.getSharedUser(), false)) {
+                    return p.getSharedUser().userId;
+                }
             } else {
                 removeAppIdLPw(p.getAppId());
                 return p.getAppId();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 42c88b3..3d10b6f 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -96,8 +96,6 @@
 /**
  * Package information used by {@link ShortcutService}.
  * User information used by {@link ShortcutService}.
- *
- * All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
  */
 class ShortcutPackage extends ShortcutPackageItem {
     private static final String TAG = ShortcutService.TAG;
@@ -162,11 +160,19 @@
     private final Executor mExecutor;
 
     /**
-     * An temp in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
+     * An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs.
      */
+    @GuardedBy("mLock")
     final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
 
     /**
+     * A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on
+     * IDs.
+     */
+    @GuardedBy("mLock")
+    private ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0);
+
+    /**
      * All the share targets from the package
      */
     private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0);
@@ -330,6 +336,15 @@
         }
     }
 
+    public void ensureAllShortcutsVisibleToLauncher(@NonNull List<ShortcutInfo> shortcuts) {
+        for (ShortcutInfo shortcut : shortcuts) {
+            if (!shortcut.isIncludedIn(ShortcutInfo.SURFACE_LAUNCHER)) {
+                throw new IllegalArgumentException("Shortcut ID=" + shortcut.getId()
+                        + " is hidden from launcher and may not be manipulated via APIs");
+            }
+        }
+    }
+
     /**
      * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible.
      */
@@ -384,7 +399,15 @@
                     & (ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_CACHED_ALL));
         }
 
-        forceReplaceShortcutInner(newShortcut);
+        if (!newShortcut.isIncludedIn(ShortcutInfo.SURFACE_LAUNCHER)) {
+            if (isAppSearchEnabled()) {
+                synchronized (mLock) {
+                    mTransientShortcuts.put(newShortcut.getId(), newShortcut);
+                }
+            }
+        } else {
+            forceReplaceShortcutInner(newShortcut);
+        }
         return oldShortcut != null;
     }
 
@@ -444,7 +467,15 @@
                     & (ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_CACHED_ALL));
         }
 
-        forceReplaceShortcutInner(newShortcut);
+        if (!newShortcut.isIncludedIn(ShortcutInfo.SURFACE_LAUNCHER)) {
+            if (isAppSearchEnabled()) {
+                synchronized (mLock) {
+                    mTransientShortcuts.put(newShortcut.getId(), newShortcut);
+                }
+            }
+        } else {
+            forceReplaceShortcutInner(newShortcut);
+        }
         if (isAppSearchEnabled()) {
             runAsSystem(() -> fromAppSearch().thenAccept(session ->
                     session.reportUsage(new ReportUsageRequest.Builder(
@@ -669,7 +700,6 @@
         forEachShortcutMutate(si -> {
             if (!pinnedShortcuts.contains(si.getId()) && si.isPinned()) {
                 si.clearFlags(ShortcutInfo.FLAG_PINNED);
-                return;
             }
         });
 
@@ -1704,8 +1734,15 @@
             for (int j = 0; j < shareTargetSize; j++) {
                 mShareTargets.get(j).saveToXml(out);
             }
-            saveShortcutsAsync(mShortcuts.values().stream().filter(ShortcutInfo::usesQuota)
-                    .collect(Collectors.toList()));
+            synchronized (mLock) {
+                final Map<String, ShortcutInfo> copy = mShortcuts;
+                if (!mTransientShortcuts.isEmpty()) {
+                    copy.putAll(mTransientShortcuts);
+                    mTransientShortcuts.clear();
+                }
+                saveShortcutsAsync(copy.values().stream().filter(ShortcutInfo::usesQuota).collect(
+                        Collectors.toList()));
+            }
         }
 
         out.endTag(null, TAG_ROOT);
@@ -2233,26 +2270,6 @@
         }
     }
 
-    void updateVisibility(String packageName, byte[] certificate, boolean visible) {
-        if (!isAppSearchEnabled()) {
-            return;
-        }
-        if (visible) {
-            mPackageIdentifiers.put(packageName, new PackageIdentifier(packageName, certificate));
-        } else {
-            mPackageIdentifiers.remove(packageName);
-        }
-        synchronized (mLock) {
-            mIsAppSearchSchemaUpToDate = false;
-        }
-        final long callingIdentity = Binder.clearCallingIdentity();
-        try {
-            fromAppSearch();
-        } finally {
-            Binder.restoreCallingIdentity(callingIdentity);
-        }
-    }
-
     void mutateShortcut(@NonNull final String id, @Nullable final ShortcutInfo shortcut,
             @NonNull final Consumer<ShortcutInfo> transform) {
         Objects.requireNonNull(id);
@@ -2358,6 +2375,7 @@
                 .addFilterSchemas(AppSearchShortcutInfo.SCHEMA_TYPE)
                 .addFilterNamespaces(getPackageName())
                 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .setResultCountPerPage(mShortcutUser.mService.getMaxActivityShortcuts())
                 .build();
     }
 
@@ -2451,6 +2469,9 @@
 
     @VisibleForTesting
     void getTopShortcutsFromPersistence(AndroidFuture<List<ShortcutInfo>> cb) {
+        if (!isAppSearchEnabled()) {
+            cb.complete(null);
+        }
         runAsSystem(() -> fromAppSearch().thenAccept(session -> {
             SearchResults res = session.search("", getSearchSpec());
             res.getNextPage(mShortcutUser.mExecutor, results -> {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 85b7435..a482f9a 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2014,6 +2014,7 @@
 
             ps.ensureImmutableShortcutsNotIncluded(newShortcuts, /*ignoreInvisible=*/ true);
             ps.ensureNoBitmapIconIfShortcutIsLongLived(newShortcuts);
+            ps.ensureAllShortcutsVisibleToLauncher(newShortcuts);
 
             // For update, don't fill in the default activity.  Having null activity means
             // "don't update the activity" here.
@@ -2214,6 +2215,9 @@
             IntentSender resultIntent, int userId, AndroidFuture<String> ret) {
         Objects.requireNonNull(shortcut);
         Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
+        Preconditions.checkArgument(
+                shortcut.isIncludedIn(ShortcutInfo.SURFACE_LAUNCHER),
+                "Shortcut excluded from launcher cannot be pinned");
         ret.complete(String.valueOf(requestPinItem(
                 packageName, userId, shortcut, null, null, resultIntent)));
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index bc4c8b0..167ad3b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -839,7 +839,7 @@
     @Override
     public @NonNull List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying,
             boolean excludePreCreated) {
-        checkManageOrCreateUsersPermission("query users");
+        checkCreateUsersPermission("query users");
         return getUsersInternal(excludePartial, excludeDying, excludePreCreated);
     }
 
@@ -865,10 +865,10 @@
     public List<UserInfo> getProfiles(@UserIdInt int userId, boolean enabledOnly) {
         boolean returnFullInfo;
         if (userId != UserHandle.getCallingUserId()) {
-            checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
+            checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
             returnFullInfo = true;
         } else {
-            returnFullInfo = hasManageOrCreateUsersPermission();
+            returnFullInfo = hasCreateUsersPermission();
         }
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -898,7 +898,7 @@
     public int[] getProfileIds(@UserIdInt int userId, @Nullable String userType,
             boolean enabledOnly) {
         if (userId != UserHandle.getCallingUserId()) {
-            checkManageOrCreateUsersPermission("getting profiles related to user " + userId);
+            checkQueryOrCreateUsersPermission("getting profiles related to user " + userId);
         }
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -987,7 +987,7 @@
     @Override
     public boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) {
         if (userId == otherUserId) return true;
-        checkManageUsersPermission("check if in the same profile group");
+        checkQueryUsersPermission("check if in the same profile group");
         return isSameProfileGroupNoChecks(userId, otherUserId);
     }
 
@@ -1388,7 +1388,7 @@
 
     @Override
     public UserInfo getUserInfo(@UserIdInt int userId) {
-        checkManageOrCreateUsersPermission("query user");
+        checkQueryOrCreateUsersPermission("query user");
         synchronized (mUsersLock) {
             return userWithName(getUserInfoLU(userId));
         }
@@ -1519,7 +1519,7 @@
 
     @Override
     public boolean isProfile(@UserIdInt int userId) {
-        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
+        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isProfile();
@@ -1528,7 +1528,7 @@
 
     @Override
     public boolean isManagedProfile(@UserIdInt int userId) {
-        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isManagedProfile");
+        checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isManagedProfile");
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isManagedProfile();
@@ -1592,7 +1592,7 @@
     @Override
     public String getUserName() {
         final int callingUid = Binder.getCallingUid();
-        if (!hasManageOrCreateUsersPermission()
+        if (!hasQueryOrCreateUsersPermission()
                 && !hasPermissionGranted(
                         android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, callingUid)) {
             throw new SecurityException("You need MANAGE_USERS or CREATE_USERS or "
@@ -1628,18 +1628,59 @@
         }
     }
 
+    /**
+     * Enforces that the calling user is in the same profile group as {@code userId} or that only
+     * the system UID or root's UID or apps that have the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS INTERACT_ACROSS_USERS}
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS}
+     * can make certain calls to the UserManager.
+     *
+     * @param name used as message if SecurityException is thrown
+     * @throws SecurityException if the caller lacks the required permissions.
+     */
     private void checkManageOrInteractPermissionIfCallerInOtherProfileGroup(@UserIdInt int userId,
             String name) {
         final int callingUserId = UserHandle.getCallingUserId();
-        if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) ||
-                hasManageUsersPermission()) {
+        if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId)) {
             return;
         }
-        if (!hasPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS,
-                Binder.getCallingUid())) {
-            throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS permission "
-                    + "to: check " + name);
+        if (hasManageUsersPermission()) {
+            return;
         }
+        if (hasPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS,
+                Binder.getCallingUid())) {
+            return;
+        }
+        throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS permission "
+                + "to: check " + name);
+    }
+
+    /**
+     * Enforces that the calling user is in the same profile group as {@code userId} or that only
+     * the system UID or root's UID or apps that have the
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS INTERACT_ACROSS_USERS}
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}
+     * can make certain calls to the UserManager.
+     *
+     * @param name used as message if SecurityException is thrown
+     * @throws SecurityException if the caller lacks the required permissions.
+     */
+    private void checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(
+            @UserIdInt int userId, String name) {
+        final int callingUserId = UserHandle.getCallingUserId();
+        if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId)) {
+            return;
+        }
+        if (hasQueryUsersPermission()) {
+            return;
+        }
+        if (hasPermissionGranted(
+                Manifest.permission.INTERACT_ACROSS_USERS, Binder.getCallingUid())) {
+            return;
+        }
+        throw new SecurityException("You need INTERACT_ACROSS_USERS, MANAGE_USERS, or QUERY_USERS "
+                + "permission to: check " + name);
     }
 
     @Override
@@ -1667,7 +1708,7 @@
     @Override
     public boolean isRestricted(@UserIdInt int userId) {
         if (userId != UserHandle.getCallingUserId()) {
-            checkManageOrCreateUsersPermission("query isRestricted for user " + userId);
+            checkCreateUsersPermission("query isRestricted for user " + userId);
         }
         synchronized (mUsersLock) {
             final UserInfo userInfo = getUserInfoLU(userId);
@@ -2147,7 +2188,7 @@
     @Override
     public List<EnforcingUser> getUserRestrictionSources(
             String restrictionKey, @UserIdInt int userId) {
-        checkManageUsersPermission("getUserRestrictionSource");
+        checkQueryUsersPermission("call getUserRestrictionSources.");
 
         // Shortcut for the most common case
         if (!hasUserRestriction(restrictionKey, userId)) {
@@ -2186,7 +2227,7 @@
 
     @Override
     public boolean hasBaseUserRestriction(String restrictionKey, @UserIdInt int userId) {
-        checkManageOrCreateUsersPermission("hasBaseUserRestriction");
+        checkCreateUsersPermission("hasBaseUserRestriction");
         if (!UserRestrictionsUtils.isValidRestriction(restrictionKey)) {
             return false;
         }
@@ -2403,7 +2444,7 @@
      */
     @Override
     public boolean canAddMoreUsersOfType(String userType) {
-        checkManageOrCreateUsersPermission("check if more users can be added.");
+        checkCreateUsersPermission("check if more users can be added.");
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         return userTypeDetails != null && canAddMoreUsersOfType(userTypeDetails);
     }
@@ -2411,7 +2452,7 @@
     /** Returns whether the creation of users of the given user type is enabled on this device. */
     @Override
     public boolean isUserTypeEnabled(String userType) {
-        checkManageOrCreateUsersPermission("check if user type is enabled.");
+        checkCreateUsersPermission("check if user type is enabled.");
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         return userTypeDetails != null && userTypeDetails.isEnabled();
     }
@@ -2426,7 +2467,7 @@
     @Override
     public boolean canAddMoreProfilesToUser(String userType, @UserIdInt int userId,
             boolean allowedToRemoveOne) {
-        checkManageUsersPermission("check if more profiles can be added.");
+        checkQueryUsersPermission("check if more profiles can be added.");
         final UserTypeDetails type = mUserTypes.get(userType);
         if (type == null || !type.isEnabled()) {
             return false;
@@ -2536,24 +2577,58 @@
      *
      * @param message used as message if SecurityException is thrown
      * @throws SecurityException if the caller is not system or root
-     * @see #hasManageOrCreateUsersPermission()
+     * @see #hasCreateUsersPermission()
      */
-    private static final void checkManageOrCreateUsersPermission(String message) {
-        if (!hasManageOrCreateUsersPermission()) {
+    private static final void checkCreateUsersPermission(String message) {
+        if (!hasCreateUsersPermission()) {
             throw new SecurityException(
                     "You either need MANAGE_USERS or CREATE_USERS permission to: " + message);
         }
     }
 
     /**
-     * Similar to {@link #checkManageOrCreateUsersPermission(String)} but when the caller is tries
+    * Enforces that only the system UID or root's UID or apps that have the
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}
+     * can make certain calls to the UserManager.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller lacks the required permissions.
+     */
+    private static final void checkQueryUsersPermission(String message) {
+        if (!hasQueryUsersPermission()) {
+            throw new SecurityException(
+                    "You either need MANAGE_USERS or QUERY_USERS permission to: " + message);
+        }
+    }
+
+    /**
+     * Enforces that only the system UID or root's UID or apps that have the
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
+     * {@link android.Manifest.permission#CREATE_USERS CREATE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}
+     * can make certain calls to the UserManager.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller lacks the required permissions.
+     */
+    private static final void checkQueryOrCreateUsersPermission(String message) {
+        if (!hasQueryOrCreateUsersPermission()) {
+            throw new SecurityException(
+                    "You either need MANAGE_USERS, CREATE_USERS, or QUERY_USERS permission to: "
+                            + message);
+        }
+    }
+
+    /**
+     * Similar to {@link #checkCreateUsersPermission(String)} but when the caller is tries
      * to create user/profiles other than what is allowed for
      * {@link android.Manifest.permission#CREATE_USERS CREATE_USERS} permission, then it will only
      * allow callers with {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} permission.
      */
-    private static final void checkManageOrCreateUsersPermission(int creationFlags) {
+    private static final void checkCreateUsersPermission(int creationFlags) {
         if ((creationFlags & ~ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION) == 0) {
-            if (!hasManageOrCreateUsersPermission()) {
+            if (!hasCreateUsersPermission()) {
                 throw new SecurityException("You either need MANAGE_USERS or CREATE_USERS "
                         + "permission to create an user with flags: " + creationFlags);
             }
@@ -2597,11 +2672,31 @@
      * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
      * {@link android.Manifest.permission#CREATE_USERS CREATE_USERS}.
      */
-    private static final boolean hasManageOrCreateUsersPermission() {
+    private static final boolean hasCreateUsersPermission() {
         return hasManageUsersOrPermission(android.Manifest.permission.CREATE_USERS);
     }
 
     /**
+     * @return whether the calling UID is system UID or root's UID or the calling app has the
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}.
+     */
+    private static final boolean hasQueryUsersPermission() {
+        return hasManageUsersOrPermission(android.Manifest.permission.QUERY_USERS);
+    }
+
+    /**
+     * @return whether the calling UID is system UID or root's UID or the calling app has
+     * {@link android.Manifest.permission#MANAGE_USERS MANAGE_USERS} or
+     * {@link android.Manifest.permission#CREATE_USERS CREATE_USERS} or
+     * {@link android.Manifest.permission#QUERY_USERS QUERY_USERS}.
+     */
+    private static final boolean hasQueryOrCreateUsersPermission() {
+        return hasCreateUsersPermission()
+                || hasPermissionGranted(Manifest.permission.QUERY_USERS, Binder.getCallingUid());
+    }
+
+    /**
      * Enforces that only the system UID or root's UID (on any user) can make certain calls to the
      * UserManager.
      *
@@ -3477,7 +3572,7 @@
     public UserInfo createProfileForUserWithThrow(@Nullable String name, @NonNull String userType,
             @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
             throws ServiceSpecificException {
-        checkManageOrCreateUsersPermission(flags);
+        checkCreateUsersPermission(flags);
         try {
             return createUserInternal(name, userType, flags, userId, disallowedPackages);
         } catch (UserManager.CheckedUserOperationException e) {
@@ -3493,7 +3588,7 @@
             @NonNull String userType,
             @UserInfoFlag int flags, @UserIdInt int userId, @Nullable String[] disallowedPackages)
             throws ServiceSpecificException {
-        checkManageOrCreateUsersPermission(flags);
+        checkCreateUsersPermission(flags);
         try {
             return createUserInternalUnchecked(name, userType, flags, userId,
                     /* preCreate= */ false, disallowedPackages, /* token= */ null);
@@ -3506,7 +3601,7 @@
     public UserInfo createUserWithThrow(String name, @NonNull String userType,
             @UserInfoFlag int flags)
             throws ServiceSpecificException {
-        checkManageOrCreateUsersPermission(flags);
+        checkCreateUsersPermission(flags);
         try {
             return createUserInternal(name, userType, flags, UserHandle.USER_NULL,
                     /* disallowedPackages= */ null);
@@ -3520,7 +3615,7 @@
         final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
         final int flags = userTypeDetails != null ? userTypeDetails.getDefaultUserInfoFlags() : 0;
 
-        checkManageOrCreateUsersPermission(flags);
+        checkCreateUsersPermission(flags);
 
         Preconditions.checkArgument(isUserTypeEligibleForPreCreation(userTypeDetails),
                 "cannot pre-create user of type " + userType);
@@ -3540,7 +3635,7 @@
             String userName, String userType, @UserInfoFlag int flags,
             Bitmap userIcon,
             String accountName, String accountType, PersistableBundle accountOptions) {
-        checkManageOrCreateUsersPermission(flags);
+        checkCreateUsersPermission(flags);
 
         if (someUserHasAccountNoChecks(accountName, accountType)) {
             throw new ServiceSpecificException(
@@ -3985,7 +4080,7 @@
 
     @Override
     public String[] getPreInstallableSystemPackages(@NonNull String userType) {
-        checkManageOrCreateUsersPermission("getPreInstallableSystemPackages");
+        checkCreateUsersPermission("getPreInstallableSystemPackages");
         final Set<String> installableSystemPackages =
                 mSystemPackageInstaller.getInstallablePackagesForUserType(userType);
         if (installableSystemPackages == null) {
@@ -4110,7 +4205,7 @@
      */
     @Override
     public UserInfo createRestrictedProfileWithThrow(@Nullable String name, int parentUserId) {
-        checkManageOrCreateUsersPermission("setupRestrictedProfile");
+        checkCreateUsersPermission("setupRestrictedProfile");
         final UserInfo user = createProfileForUserWithThrow(
                 name, UserManager.USER_TYPE_FULL_RESTRICTED, 0, parentUserId, null);
         if (user == null) {
@@ -4207,7 +4302,7 @@
     @Override
     public boolean removeUser(@UserIdInt int userId) {
         Slog.i(LOG_TAG, "removeUser u" + userId);
-        checkManageOrCreateUsersPermission("Only the system can remove users");
+        checkCreateUsersPermission("Only the system can remove users");
 
         final String restriction = getUserRemovalRestriction(userId);
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
@@ -4219,7 +4314,7 @@
 
     @Override
     public boolean removeUserEvenWhenDisallowed(@UserIdInt int userId) {
-        checkManageOrCreateUsersPermission("Only the system can remove users");
+        checkCreateUsersPermission("Only the system can remove users");
         return removeUserUnchecked(userId);
     }
 
@@ -4334,7 +4429,7 @@
     @Override
     public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId,
             boolean evenWhenDisallowed) {
-        checkManageOrCreateUsersPermission("Only the system can remove users");
+        checkCreateUsersPermission("Only the system can remove users");
 
         if (!evenWhenDisallowed) {
             final String restriction = getUserRemovalRestriction(userId);
@@ -5085,7 +5180,7 @@
 
     @Override
     public boolean someUserHasAccount(String accountName, String accountType) {
-        checkManageOrCreateUsersPermission("check seed account information");
+        checkCreateUsersPermission("check seed account information");
         return someUserHasAccountNoChecks(accountName, accountType);
     }
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 328a55f..0f3b4bc 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -143,8 +143,9 @@
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_CHANGE_WIFI_STATE,
             UserManager.DISALLOW_WIFI_TETHERING,
-            UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI
-
+            UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG
     });
 
     public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -190,7 +191,9 @@
             UserManager.DISALLOW_MICROPHONE_TOGGLE,
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_CHANGE_WIFI_STATE,
-            UserManager.DISALLOW_WIFI_TETHERING
+            UserManager.DISALLOW_WIFI_TETHERING,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG
     );
 
     /**
@@ -227,7 +230,9 @@
                     UserManager.DISALLOW_CONFIG_DATE_TIME,
                     UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
                     UserManager.DISALLOW_CHANGE_WIFI_STATE,
-                    UserManager.DISALLOW_WIFI_TETHERING
+                    UserManager.DISALLOW_WIFI_TETHERING,
+                    UserManager.DISALLOW_WIFI_DIRECT,
+                    UserManager.DISALLOW_ADD_WIFI_CONFIG
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/VerificationParams.java b/services/core/java/com/android/server/pm/VerificationParams.java
index 6d68139..4334cbd 100644
--- a/services/core/java/com/android/server/pm/VerificationParams.java
+++ b/services/core/java/com/android/server/pm/VerificationParams.java
@@ -30,6 +30,7 @@
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_INTEGRITY_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.CHECK_PENDING_VERIFICATION;
 import static com.android.server.pm.PackageManagerService.DEBUG_VERIFY;
+import static com.android.server.pm.PackageManagerService.DEFAULT_VERIFICATION_RESPONSE;
 import static com.android.server.pm.PackageManagerService.ENABLE_ROLLBACK_TIMEOUT;
 import static com.android.server.pm.PackageManagerService.PACKAGE_MIME_TYPE;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -59,10 +60,13 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Pair;
 import android.util.Slog;
@@ -76,6 +80,11 @@
 
 final class VerificationParams extends HandlerParams {
     /**
+     * Whether verification is enabled by default.
+     */
+    private static final boolean DEFAULT_VERIFY_ENABLE = true;
+
+    /**
      * Whether integrity verification is enabled by default.
      */
     private static final boolean DEFAULT_INTEGRITY_VERIFY_ENABLE = true;
@@ -333,157 +342,289 @@
         if (verifierUser == UserHandle.ALL) {
             verifierUser = UserHandle.SYSTEM;
         }
+        final int verifierUserId = verifierUser.getIdentifier();
+
+        String requiredVerifierPackage = mPm.mRequiredVerifierPackage;
+        boolean requiredVerifierPackageOverridden = false;
+
+        // Allow verifier override for ADB installations which could already be unverified using
+        // PackageManager.INSTALL_DISABLE_VERIFICATION flag.
+        if ((mInstallFlags & PackageManager.INSTALL_FROM_ADB) != 0
+                && (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) == 0) {
+            final String adbVerifierOverridePackage = SystemProperties.get(
+                    "debug.pm.adb_verifier_override_package", "");
+            // Check if the package installed.
+            if (!TextUtils.isEmpty(adbVerifierOverridePackage)
+                    && packageExists(adbVerifierOverridePackage)) {
+                // Pretend we requested to disable verification from command line.
+                boolean requestedDisableVerification = true;
+                // If this returns false then the caller can already skip verification, so we are
+                // not adding a new way to disable verifications.
+                if (!isAdbVerificationEnabled(pkgLite, verifierUserId,
+                        requestedDisableVerification)) {
+                    requiredVerifierPackage = adbVerifierOverridePackage;
+                    requiredVerifierPackageOverridden = true;
+                }
+            }
+        }
 
         /*
          * Determine if we have any installed package verifiers. If we
          * do, then we'll defer to them to verify the packages.
          */
-        final int requiredUid = mPm.mRequiredVerifierPackage == null ? -1
-                : mPm.getPackageUid(mPm.mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
-                        verifierUser.getIdentifier());
+        final int requiredUid = requiredVerifierPackage == null ? -1
+                : mPm.getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING,
+                        verifierUserId);
         verificationState.setRequiredVerifierUid(requiredUid);
-        final int installerUid =
-                mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
-        final boolean isVerificationEnabled = mInstallPackageHelper.isVerificationEnabled(
-                pkgLite, verifierUser.getIdentifier(), mInstallFlags, installerUid);
-        final boolean isV4Signed =
-                (mSigningDetails.getSignatureSchemeVersion() == SIGNING_BLOCK_V4);
-        final boolean isIncrementalInstall =
-                (mDataLoaderType == DataLoaderType.INCREMENTAL);
-        // NOTE: We purposefully skip verification for only incremental installs when there's
-        // a v4 signature block. Otherwise, proceed with verification as usual.
-        if (!mOriginInfo.mExisting
-                && isVerificationEnabled
-                && (!isIncrementalInstall || !isV4Signed)) {
-            final Intent verification = new Intent(
-                    Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
-            verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            verification.setDataAndType(Uri.fromFile(new File(mOriginInfo.mResolvedPath)),
-                    PACKAGE_MIME_TYPE);
-            verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        final boolean isVerificationEnabled = isVerificationEnabled(pkgLite,
+                verifierUserId);
 
-            // Query all live verifiers based on current user state
-            final ParceledListSlice<ResolveInfo> receivers = mPm.queryIntentReceivers(verification,
-                    PACKAGE_MIME_TYPE, 0, verifierUser.getIdentifier());
+        if (mOriginInfo.mExisting || !isVerificationEnabled) {
+            verificationState.setVerifierResponse(requiredUid, PackageManager.VERIFICATION_ALLOW);
+            return;
+        }
 
-            if (DEBUG_VERIFY) {
-                Slog.d(TAG, "Found " + receivers.getList().size() + " verifiers for intent "
-                        + verification.toString() + " with " + pkgLite.verifiers.length
-                        + " optional verifiers");
-            }
+        final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+        verification.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        verification.setDataAndType(Uri.fromFile(new File(mOriginInfo.mResolvedPath)),
+                PACKAGE_MIME_TYPE);
+        verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
-            verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
+        // Query all live verifiers based on current user state
+        final ParceledListSlice<ResolveInfo> receivers = mPm.queryIntentReceivers(verification,
+                PACKAGE_MIME_TYPE, 0, verifierUserId);
 
-            verification.putExtra(
-                    PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, mInstallFlags);
+        if (DEBUG_VERIFY) {
+            Slog.d(TAG, "Found " + receivers.getList().size() + " verifiers for intent "
+                    + verification.toString() + " with " + pkgLite.verifiers.length
+                    + " optional verifiers");
+        }
 
-            verification.putExtra(
-                    PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME, pkgLite.packageName);
+        verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
 
-            verification.putExtra(
-                    PackageManager.EXTRA_VERIFICATION_VERSION_CODE, pkgLite.versionCode);
+        verification.putExtra(
+                PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, mInstallFlags);
 
-            verification.putExtra(
-                    PackageManager.EXTRA_VERIFICATION_LONG_VERSION_CODE,
-                    pkgLite.getLongVersionCode());
+        verification.putExtra(
+                PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME, pkgLite.packageName);
 
-            final String baseCodePath = mPackageLite.getBaseApkPath();
-            final String[] splitCodePaths = mPackageLite.getSplitApkPaths();
-            final String rootHashString =
-                    PackageManagerServiceUtils.buildVerificationRootHashString(baseCodePath,
-                            splitCodePaths);
+        verification.putExtra(
+                PackageManager.EXTRA_VERIFICATION_VERSION_CODE, pkgLite.versionCode);
 
-            if (rootHashString != null) {
-                verification.putExtra(PackageManager.EXTRA_VERIFICATION_ROOT_HASH, rootHashString);
-            }
+        verification.putExtra(
+                PackageManager.EXTRA_VERIFICATION_LONG_VERSION_CODE,
+                pkgLite.getLongVersionCode());
 
-            verification.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, mDataLoaderType);
+        final String baseCodePath = mPackageLite.getBaseApkPath();
+        final String[] splitCodePaths = mPackageLite.getSplitApkPaths();
+        final String rootHashString = PackageManagerServiceUtils.buildVerificationRootHashString(
+                baseCodePath, splitCodePaths);
 
-            verification.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
+        if (rootHashString != null) {
+            verification.putExtra(PackageManager.EXTRA_VERIFICATION_ROOT_HASH, rootHashString);
+        }
 
-            populateInstallerExtras(verification);
+        verification.putExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, mDataLoaderType);
 
-            final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
-                    receivers.getList(), verificationState);
+        verification.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
 
-            DeviceIdleInternal idleController =
-                    mPm.mInjector.getLocalService(DeviceIdleInternal.class);
-            final long idleDuration = VerificationUtils.getVerificationTimeout(mPm.mContext);
-            final BroadcastOptions options = BroadcastOptions.makeBasic();
-            options.setTemporaryAppAllowlist(idleDuration,
-                    TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
-                    REASON_PACKAGE_VERIFIER, "");
+        populateInstallerExtras(verification);
 
-            /*
-             * If any sufficient verifiers were listed in the package
-             * manifest, attempt to ask them.
-             */
-            if (sufficientVerifiers != null) {
-                final int n = sufficientVerifiers.size();
-                if (n == 0) {
-                    String errorMsg = "Additional verifiers required, but none installed.";
-                    Slog.i(TAG, errorMsg);
-                    setReturnCode(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
-                } else {
-                    for (int i = 0; i < n; i++) {
-                        final ComponentName verifierComponent = sufficientVerifiers.get(i);
-                        idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
-                                verifierComponent.getPackageName(), idleDuration,
-                                verifierUser.getIdentifier(), false,
-                                REASON_PACKAGE_VERIFIER, "package verifier");
+        // Streaming installation timeout schema is enabled only for:
+        // 1. Incremental installs with v4,
+        // 2. If device/policy allow unverified app installs by default.
+        final boolean streaming = (mDataLoaderType == DataLoaderType.INCREMENTAL)
+                && (mSigningDetails.getSignatureSchemeVersion() == SIGNING_BLOCK_V4)
+                && (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW);
 
-                        final Intent sufficientIntent = new Intent(verification);
-                        sufficientIntent.setComponent(verifierComponent);
-                        mPm.mContext.sendBroadcastAsUser(sufficientIntent, verifierUser,
-                                /* receiverPermission= */ null,
-                                options.toBundle());
-                    }
+        final long verificationTimeout = VerificationUtils.getVerificationTimeout(mPm.mContext,
+                streaming);
+
+        final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
+                receivers.getList(), verificationState);
+
+        DeviceIdleInternal idleController =
+                mPm.mInjector.getLocalService(DeviceIdleInternal.class);
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setTemporaryAppAllowlist(verificationTimeout,
+                TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                REASON_PACKAGE_VERIFIER, "");
+
+        /*
+         * If any sufficient verifiers were listed in the package
+         * manifest, attempt to ask them.
+         */
+        if (sufficientVerifiers != null) {
+            final int n = sufficientVerifiers.size();
+            if (n == 0) {
+                String errorMsg = "Additional verifiers required, but none installed.";
+                Slog.i(TAG, errorMsg);
+                setReturnCode(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, errorMsg);
+            } else {
+                for (int i = 0; i < n; i++) {
+                    final ComponentName verifierComponent = sufficientVerifiers.get(i);
+                    idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
+                            verifierComponent.getPackageName(), verificationTimeout,
+                            verifierUserId, false,
+                            REASON_PACKAGE_VERIFIER, "package verifier");
+
+                    final Intent sufficientIntent = new Intent(verification);
+                    sufficientIntent.setComponent(verifierComponent);
+                    mPm.mContext.sendBroadcastAsUser(sufficientIntent, verifierUser,
+                            /* receiverPermission= */ null,
+                            options.toBundle());
                 }
             }
+        }
 
-            if (mPm.mRequiredVerifierPackage != null) {
-                final ComponentName requiredVerifierComponent = matchComponentForVerifier(
-                        mPm.mRequiredVerifierPackage, receivers.getList());
-                /*
-                 * Send the intent to the required verification agent,
-                 * but only start the verification timeout after the
-                 * target BroadcastReceivers have run.
-                 */
-                verification.setComponent(requiredVerifierComponent);
-                idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
-                        mPm.mRequiredVerifierPackage, idleDuration,
-                        verifierUser.getIdentifier(), false,
-                        REASON_PACKAGE_VERIFIER, "package verifier");
-                mPm.mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
-                        android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
-                        /* appOp= */ AppOpsManager.OP_NONE,
-                        /* options= */ options.toBundle(),
-                        new BroadcastReceiver() {
-                            @Override
-                            public void onReceive(Context context, Intent intent) {
-                                final Message msg = mPm.mHandler
-                                        .obtainMessage(CHECK_PENDING_VERIFICATION);
-                                msg.arg1 = verificationId;
-                                mPm.mHandler.sendMessageDelayed(msg,
-                                        VerificationUtils.getVerificationTimeout(mPm.mContext));
-                            }
-                        }, null, 0, null, null);
+        if (requiredVerifierPackage == null) {
+            Slog.e(TAG, "Required verifier is null");
+            return;
+        }
 
-                Trace.asyncTraceBegin(
-                        TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
-
-                /*
-                 * We don't want the copy to proceed until verification
-                 * succeeds.
-                 */
-                mWaitForVerificationToComplete = true;
-            }
+        final int verificationCodeAtTimeout;
+        if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) {
+            verificationCodeAtTimeout = PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT;
         } else {
-            verificationState.setVerifierResponse(
-                    requiredUid, PackageManager.VERIFICATION_ALLOW);
+            verificationCodeAtTimeout = PackageManager.VERIFICATION_REJECT;
+        }
+        final PackageVerificationResponse response = new PackageVerificationResponse(
+                verificationCodeAtTimeout, requiredUid);
+
+        /*
+         * Send the intent to the required verification agent,
+         * but only start the verification timeout after the
+         * target BroadcastReceivers have run.
+         */
+        if (!requiredVerifierPackageOverridden) {
+            final ComponentName requiredVerifierComponent = matchComponentForVerifier(
+                    requiredVerifierPackage, receivers.getList());
+            verification.setComponent(requiredVerifierComponent);
+        } else {
+            verification.setPackage(requiredVerifierPackage);
+        }
+        idleController.addPowerSaveTempWhitelistApp(Process.myUid(),
+                requiredVerifierPackage, verificationTimeout,
+                verifierUserId, false,
+                REASON_PACKAGE_VERIFIER, "package verifier");
+
+        if (streaming) {
+            // For streaming installations, count verification timeout from the broadcast.
+            startVerificationTimeoutCountdown(verificationId, streaming, response,
+                    verificationTimeout);
+        }
+
+        mPm.mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                /* appOp= */ AppOpsManager.OP_NONE,
+                /* options= */ options.toBundle(),
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (!streaming) {
+                            // For NON-streaming installations, count verification timeout from
+                            // the broadcast was processed by all receivers.
+                            startVerificationTimeoutCountdown(verificationId, streaming, response,
+                                    verificationTimeout);
+                        }
+                    }
+                }, null, 0, null, null);
+
+        Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "verification", verificationId);
+
+        /*
+         * We don't want the copy to proceed until verification
+         * succeeds.
+         */
+        mWaitForVerificationToComplete = true;
+    }
+
+    private void startVerificationTimeoutCountdown(int verificationId, boolean streaming,
+            PackageVerificationResponse response, long verificationTimeout) {
+        final Message msg = mPm.mHandler.obtainMessage(CHECK_PENDING_VERIFICATION);
+        msg.arg1 = verificationId;
+        msg.arg2 = streaming ? 1 : 0;
+        msg.obj = response;
+        mPm.mHandler.sendMessageDelayed(msg, verificationTimeout);
+    }
+
+    /**
+     * Get the default verification agent response code.
+     *
+     * @return default verification response code
+     */
+    int getDefaultVerificationResponse() {
+        if (mPm.mUserManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS,
+                getUser().getIdentifier())) {
+            return PackageManager.VERIFICATION_REJECT;
+        }
+        return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+                DEFAULT_VERIFICATION_RESPONSE);
+    }
+
+    private boolean packageExists(String packageName) {
+        synchronized (mPm.mLock) {
+            return mPm.mSettings.getPackageLPr(packageName) != null;
         }
     }
 
+    private boolean isAdbVerificationEnabled(PackageInfoLite pkgInfoLite, int userId,
+            boolean requestedDisableVerification) {
+        if (mPm.isUserRestricted(userId, UserManager.ENSURE_VERIFY_APPS)) {
+            return true;
+        }
+        // Check if the developer wants to skip verification for ADB installs
+        if (requestedDisableVerification) {
+            if (!packageExists(pkgInfoLite.packageName)) {
+                // Always verify fresh install
+                return true;
+            }
+            // Only skip when apk is debuggable
+            return !pkgInfoLite.debuggable;
+        }
+        return android.provider.Settings.Global.getInt(mPm.mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) != 0;
+    }
+
+    /**
+     * Check whether package verification has been enabled.
+     *
+     * @return true if verification should be performed
+     */
+    private boolean isVerificationEnabled(PackageInfoLite pkgInfoLite, int userId) {
+        if (!DEFAULT_VERIFY_ENABLE) {
+            return false;
+        }
+
+        final int installerUid = mVerificationInfo == null ? -1 : mVerificationInfo.mInstallerUid;
+        final int installFlags = mInstallFlags;
+
+        // Check if installing from ADB
+        if ((installFlags & PackageManager.INSTALL_FROM_ADB) != 0) {
+            boolean requestedDisableVerification =
+                    (mInstallFlags & PackageManager.INSTALL_DISABLE_VERIFICATION) != 0;
+            return isAdbVerificationEnabled(pkgInfoLite, userId, requestedDisableVerification);
+        }
+
+        // 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) {
+            if (mPm.mInstantAppInstallerActivity != null
+                    && mPm.mInstantAppInstallerActivity.packageName.equals(
+                    mPm.mRequiredVerifierPackage)) {
+                try {
+                    mPm.mInjector.getSystemService(AppOpsManager.class)
+                            .checkPackage(installerUid, mPm.mRequiredVerifierPackage);
+                    if (DEBUG_VERIFY) {
+                        Slog.i(TAG, "disable verification for instant app");
+                    }
+                    return false;
+                } catch (SecurityException ignore) { }
+            }
+        }
+        return true;
+    }
 
     private List<ComponentName> matchVerifiers(PackageInfoLite pkgInfo,
             List<ResolveInfo> receivers, final PackageVerificationState verificationState) {
diff --git a/services/core/java/com/android/server/pm/VerificationUtils.java b/services/core/java/com/android/server/pm/VerificationUtils.java
index 4392b47..c132028 100644
--- a/services/core/java/com/android/server/pm/VerificationUtils.java
+++ b/services/core/java/com/android/server/pm/VerificationUtils.java
@@ -35,12 +35,25 @@
     private static final long DEFAULT_VERIFICATION_TIMEOUT = 10 * 1000;
 
     /**
-     * Get the verification agent timeout.  Used for both the APK verifier and the
+     * The default maximum time to wait for the verification agent to return in
+     * milliseconds.
+     */
+    private static final long DEFAULT_STREAMING_VERIFICATION_TIMEOUT = 3 * 1000;
+
+    public static long getVerificationTimeout(Context context, boolean streaming) {
+        if (streaming) {
+            return getDefaultStreamingVerificationTimeout(context);
+        }
+        return getDefaultVerificationTimeout(context);
+    }
+
+    /**
+     * Get the default verification agent timeout. Used for both the APK verifier and the
      * intent filter verifier.
      *
      * @return verification timeout in milliseconds
      */
-    public static long getVerificationTimeout(Context context) {
+    public static long getDefaultVerificationTimeout(Context context) {
         long timeout = Settings.Global.getLong(context.getContentResolver(),
                 Settings.Global.PACKAGE_VERIFIER_TIMEOUT, DEFAULT_VERIFICATION_TIMEOUT);
         // The setting can be used to increase the timeout but not decrease it, since that is
@@ -48,6 +61,20 @@
         return Math.max(timeout, DEFAULT_VERIFICATION_TIMEOUT);
     }
 
+    /**
+     * Get the default verification agent timeout for streaming installations.
+     *
+     * @return verification timeout in milliseconds
+     */
+    public static long getDefaultStreamingVerificationTimeout(Context context) {
+        long timeout = Settings.Global.getLong(context.getContentResolver(),
+                Settings.Global.PACKAGE_STREAMING_VERIFIER_TIMEOUT,
+                DEFAULT_STREAMING_VERIFICATION_TIMEOUT);
+        // The setting can be used to increase the timeout but not decrease it, since that is
+        // equivalent to disabling the verifier.
+        return Math.max(timeout, DEFAULT_STREAMING_VERIFICATION_TIMEOUT);
+    }
+
     public static void broadcastPackageVerified(int verificationId, Uri packageUri,
             int verificationCode, @Nullable String rootHashString, int dataLoaderType,
             UserHandle user, Context context) {
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 bcb5e72..07cc3d0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -84,7 +84,7 @@
      */
     @Nullable
     public static PackageInfo generate(AndroidPackage pkg, int[] gids,
-            @PackageManager.PackageInfoFlags long flags, long firstInstallTime,
+            @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
@@ -105,7 +105,7 @@
      * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
      */
     private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
-            @PackageManager.PackageInfoFlags long flags, long firstInstallTime,
+            @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserState state, int userId,
             @Nullable ApexInfo apexInfo, @Nullable PackageStateInternal pkgSetting) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
@@ -209,7 +209,7 @@
      */
     @Nullable
     public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
-            @PackageManager.ApplicationInfoFlags long flags, @NonNull PackageUserState state,
+            @PackageManager.ApplicationInfoFlagsBits long flags, @NonNull PackageUserState state,
             int userId, @Nullable PackageStateInternal pkgSetting) {
         if (pkg == null) {
             return null;
@@ -255,7 +255,7 @@
      */
     @Nullable
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlags long flags, PackageUserState state, int userId,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
     }
@@ -265,7 +265,7 @@
      */
     @Nullable
     private static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
-            @PackageManager.ComponentInfoFlags long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
             @Nullable ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (a == null) return null;
@@ -291,7 +291,7 @@
      */
     @Nullable
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlags long flags, PackageUserState state, int userId,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
     }
@@ -301,7 +301,7 @@
      */
     @Nullable
     private static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
-            @PackageManager.ComponentInfoFlags long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
             @Nullable ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (s == null) return null;
@@ -326,7 +326,7 @@
      */
     @Nullable
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
-            @PackageManager.ComponentInfoFlags long flags, PackageUserState state,
+            @PackageManager.ComponentInfoFlagsBits long flags, PackageUserState state,
             @NonNull ApplicationInfo applicationInfo, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (p == null) return null;
@@ -353,7 +353,7 @@
      */
     @Nullable
     public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
-            AndroidPackage pkg, @PackageManager.ComponentInfoFlags long flags, int userId,
+            AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags, int userId,
             @Nullable PackageStateInternal pkgSetting) {
         if (i == null) return null;
 
@@ -381,7 +381,7 @@
     //  PackageStateInternal os that checkUseInstalledOrHidden filter can apply
     @Nullable
     public static PermissionInfo generatePermissionInfo(ParsedPermission p,
-            @PackageManager.ComponentInfoFlags long flags) {
+            @PackageManager.ComponentInfoFlagsBits long flags) {
         // TODO(b/135203078): Remove null checks and make all usages @NonNull
         if (p == null) return null;
 
@@ -391,7 +391,7 @@
 
     @Nullable
     public static PermissionGroupInfo generatePermissionGroupInfo(ParsedPermissionGroup pg,
-            @PackageManager.ComponentInfoFlags long flags) {
+            @PackageManager.ComponentInfoFlagsBits long flags) {
         if (pg == null) return null;
 
         // For now, permissions don't have state-adjustable fields; return directly
@@ -400,7 +400,7 @@
 
     @Nullable
     public static ArrayMap<String, ProcessInfo> generateProcessInfo(
-            Map<String, ParsedProcess> procs, @PackageManager.ComponentInfoFlags long flags) {
+            Map<String, ParsedProcess> procs, @PackageManager.ComponentInfoFlagsBits long flags) {
         if (procs == null) {
             return null;
         }
@@ -423,7 +423,7 @@
      */
     public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
             PackageStateInternal pkgSetting, PackageUserState state,
-            @PackageManager.PackageInfoFlags long flags) {
+            @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()
@@ -628,8 +628,8 @@
          */
         @Nullable
         public ApplicationInfo generate(AndroidPackage pkg,
-                @PackageManager.ApplicationInfoFlags long flags, PackageUserState state, int userId,
-                @Nullable PackageStateInternal pkgSetting) {
+                @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserState state,
+                int userId, @Nullable PackageStateInternal pkgSetting) {
             ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
             if (appInfo != null) {
                 return appInfo;
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 c87a530..6c1ef2e 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -103,12 +103,12 @@
     private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
     private static final boolean DEBUG = false;
 
-    @PackageManager.ResolveInfoFlags
+    @PackageManager.ResolveInfoFlagsBits
     private static final int DEFAULT_INTENT_QUERY_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.MATCH_UNINSTALLED_PACKAGES;
 
-    @PackageManager.PackageInfoFlags
+    @PackageManager.PackageInfoFlagsBits
     private static final int DEFAULT_PACKAGE_INFO_QUERY_FLAGS =
             PackageManager.MATCH_UNINSTALLED_PACKAGES
                     | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
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 75f3725..041c4fe 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -431,6 +431,8 @@
                 }
             }
         }
+        boolean wasNonInternal = permission != null && permission.mType != TYPE_CONFIG
+                && !permission.isInternal();
         boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
                 && !permission.isRuntime();
         if (permission == null) {
@@ -476,10 +478,10 @@
             r.append("DUP:");
             r.append(permissionInfo.name);
         }
-        if ((permission.isInternal() && ownerChanged)
+        if ((permission.isInternal() && (ownerChanged || wasNonInternal))
                 || (permission.isRuntime() && (ownerChanged || wasNonRuntime))) {
             // If this is an internal/runtime permission and the owner has changed, or this wasn't a
-            // runtime permission, then permission state should be cleaned up.
+            // internal/runtime permission, then permission state should be cleaned up.
             permission.mDefinitionChanged = true;
         }
         if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
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 4830691..0958bcb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -16,12 +16,9 @@
 
 package com.android.server.pm.permission;
 
-import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
 import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.Manifest.permission.RECORD_AUDIO;
 import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
 import static android.app.AppOpsManager.ATTRIBUTION_FLAGS_NONE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
@@ -29,43 +26,11 @@
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISALLOWED;
 import static android.content.pm.ApplicationInfo.AUTO_REVOKE_DISCOURAGED;
-import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE;
-import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL;
-import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
-import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
-import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED;
 
-import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE;
-import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
-import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
-import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
-import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
-
 import android.Manifest;
 import android.annotation.AppIdInt;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -73,119 +38,49 @@
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.AttributionFlags;
 import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManagerInternal;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.content.AttributionSource;
 import android.content.AttributionSourceState;
 import android.content.Context;
 import android.content.PermissionChecker;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.PermissionGroupInfoFlags;
-import android.content.pm.PackageManager.PermissionInfoFlags;
-import android.content.pm.PackageManager.PermissionWhitelistFlags;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
-import android.content.pm.SigningDetails;
-import android.content.pm.parsing.component.ComponentMutateUtils;
-import android.content.pm.parsing.component.ParsedPermission;
-import android.content.pm.parsing.component.ParsedPermissionGroup;
-import android.content.pm.parsing.component.ParsedPermissionUtils;
-import android.content.pm.permission.CompatibilityPermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
-import android.metrics.LogMaker;
-import android.os.AsyncTask;
 import android.os.Binder;
-import android.os.Build;
-import android.os.Debug;
-import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.Process;
-import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.permission.IOnPermissionsChangeListener;
 import android.permission.IPermissionChecker;
 import android.permission.IPermissionManager;
 import android.permission.PermissionCheckerManager;
-import android.permission.PermissionControllerManager;
 import android.permission.PermissionManager;
 import android.permission.PermissionManagerInternal;
-import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.DebugUtils;
-import android.util.EventLog;
-import android.util.IntArray;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.os.RoSystemProperties;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.CollectionUtils;
-import com.android.internal.util.DumpUtils;
-import com.android.internal.util.IntPair;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.TriFunction;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
-import com.android.server.Watchdog;
-import com.android.server.pm.ApexManager;
-import com.android.server.pm.PackageSetting;
 import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerService;
-import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
-import com.android.server.pm.permission.PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener;
-import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.policy.PermissionPolicyInternal;
-import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
-import libcore.util.EmptyArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiFunction;
@@ -194,68 +89,12 @@
  * Manages all permissions and handles permissions related tasks.
  */
 public class PermissionManagerService extends IPermissionManager.Stub {
-    private static final String TAG = "PackageManager";
     private static final String LOG_TAG = PermissionManagerService.class.getSimpleName();
 
-    private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60);
-
-    // For automotive products, CarService enforces allow-listing of the privileged permissions
-    // com.android.car is the package name which declares auto specific permissions
-    private static final String CAR_PACKAGE_NAME = "com.android.car";
-
-    /** Cap the size of permission trees that 3rd party apps can define; in characters of text */
-    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;
-    /** Empty array to avoid allocations */
-    private static final int[] EMPTY_INT_ARRAY = new int[0];
-
-    /**
-     * When these flags are set, the system should not automatically modify the permission grant
-     * state.
-     */
-    private static final int BLOCKING_PERMISSION_FLAGS = FLAG_PERMISSION_SYSTEM_FIXED
-            | FLAG_PERMISSION_POLICY_FIXED
-            | FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-
-    /** Permission flags set by the user */
-    private static final int USER_PERMISSION_FLAGS = FLAG_PERMISSION_USER_SET
-            | FLAG_PERMISSION_USER_FIXED;
-
-    /** All storage permissions */
-    private static final List<String> STORAGE_PERMISSIONS = new ArrayList<>();
-    /** All nearby devices permissions */
-    private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();
-
-    /**
-     * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are
-     * implicitly added to a package
-     */
-    private static final List<String> IMPLICIT_GRANTED_PERMISSIONS = new ArrayList<>();
-
-    /** If the permission of the value is granted, so is the key */
-    private static final Map<String, String> FULLER_PERMISSION_MAP = new HashMap<>();
-
     /** Map of IBinder -> Running AttributionSource */
     private static final ConcurrentHashMap<IBinder, RegisteredAttribution>
             sRunningAttributionSources = new ConcurrentHashMap<>();
 
-    static {
-        FULLER_PERMISSION_MAP.put(Manifest.permission.ACCESS_COARSE_LOCATION,
-                Manifest.permission.ACCESS_FINE_LOCATION);
-        FULLER_PERMISSION_MAP.put(Manifest.permission.INTERACT_ACROSS_USERS,
-                Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
-        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
-        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
-        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
-        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
-        IMPLICIT_GRANTED_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
-    }
-
-    /** Set of source package names for Privileged Permission Allowlist */
-    private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames =
-            new ArraySet<>();
-
     /** Lock to protect internal data access */
     private final Object mLock = new Object();
 
@@ -265,13 +104,6 @@
     /** Internal connection to the user manager */
     private final UserManagerInternal mUserManagerInt;
 
-    @GuardedBy("mLock")
-    @NonNull
-    private final DevicePermissionState mState = new DevicePermissionState();
-
-    /** Permission controller: User space permission management */
-    private PermissionControllerManager mPermissionControllerManager;
-
     /** Map of OneTimePermissionUserManagers keyed by userId */
     @GuardedBy("mLock")
     @NonNull
@@ -281,130 +113,18 @@
     /** App ops manager */
     private final AppOpsManager mAppOpsManager;
 
-    /**
-     * Built-in permissions. Read from system configuration files. Mapping is from
-     * UID to permission name.
-     */
-    private final SparseArray<ArraySet<String>> mSystemPermissions;
-
-    /** Built-in group IDs given to all packages. Read from system configuration files. */
-    @NonNull
-    private final int[] mGlobalGids;
-
-    private final HandlerThread mHandlerThread;
-    private final Handler mHandler;
     private final Context mContext;
-    private final MetricsLogger mMetricsLogger = new MetricsLogger();
-    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
-            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-
-    /** Internal storage for permissions and related settings */
-    @GuardedBy("mLock")
-    @NonNull
-    private final PermissionRegistry mRegistry = new PermissionRegistry();
+    private final PermissionManagerServiceImpl mPermissionManagerServiceImpl;
 
     @NonNull
     private final AttributionSourceRegistry mAttributionSourceRegistry;
 
     @GuardedBy("mLock")
-    @Nullable
-    private ArraySet<String> mPrivappPermissionsViolations;
-
-    @GuardedBy("mLock")
-    private boolean mSystemReady;
-
-    @GuardedBy("mLock")
-    private PermissionPolicyInternal mPermissionPolicyInternal;
-
-    /**
-     * A permission backup might contain apps that are not installed. In this case we delay the
-     * restoration until the app is installed.
-     *
-     * <p>This array ({@code userId -> noDelayedBackupLeft}) is {@code true} for all the users where
-     * there is <u>no more</u> delayed backup left.
-     */
-    @GuardedBy("mLock")
-    private final SparseBooleanArray mHasNoDelayedPermBackup = new SparseBooleanArray();
-
-    /** Listeners for permission state (granting and flags) changes */
-    @GuardedBy("mLock")
-    final private ArrayList<OnRuntimePermissionStateChangedListener>
-            mRuntimePermissionStateChangedListeners = new ArrayList<>();
-
-    @GuardedBy("mLock")
     private CheckPermissionDelegate mCheckPermissionDelegate;
 
-    @NonNull
-    private final OnPermissionChangeListeners mOnPermissionChangeListeners;
-
     @Nullable
     private HotwordDetectionServiceProvider mHotwordDetectionServiceProvider;
 
-    // TODO: Take a look at the methods defined in the callback.
-    // The callback was initially created to support the split between permission
-    // manager and the package manager. However, it's started to be used for other
-    // purposes. It may make sense to keep as an abstraction, but, the methods
-    // necessary to be overridden may be different than what was initially needed
-    // for the split.
-    private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() {
-        @Override
-        public void onGidsChanged(int appId, int userId) {
-            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
-        }
-        @Override
-        public void onPermissionGranted(int uid, int userId) {
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            // Not critical; if this is lost, the application has to request again.
-            mPackageManagerInt.writeSettings(true);
-        }
-        @Override
-        public void onInstallPermissionGranted() {
-            mPackageManagerInt.writeSettings(true);
-        }
-        @Override
-        public void onPermissionRevoked(int uid, int userId, String reason) {
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            // Critical; after this call the application should never have the permission
-            mPackageManagerInt.writeSettings(false);
-            final int appId = UserHandle.getAppId(uid);
-            if (reason == null) {
-                mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
-            } else {
-                mHandler.post(() -> killUid(appId, userId, reason));
-            }
-        }
-        @Override
-        public void onInstallPermissionRevoked() {
-            mPackageManagerInt.writeSettings(true);
-        }
-        @Override
-        public void onPermissionUpdated(int[] userIds, boolean sync) {
-            mPackageManagerInt.writePermissionSettings(userIds, !sync);
-        }
-        @Override
-        public void onInstallPermissionUpdated() {
-            mPackageManagerInt.writeSettings(true);
-        }
-        @Override
-        public void onPermissionRemoved() {
-            mPackageManagerInt.writeSettings(false);
-        }
-        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
-                int uid) {
-            onPermissionUpdated(updatedUserIds, sync);
-            for (int i = 0; i < updatedUserIds.length; i++) {
-                int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
-                mOnPermissionChangeListeners.onPermissionsChanged(userUid);
-            }
-        }
-        public void onInstallPermissionUpdatedNotifyListener(int uid) {
-            onInstallPermissionUpdated();
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-        }
-    };
-
     PermissionManagerService(@NonNull Context context,
             @NonNull ArrayMap<String, FeatureInfo> availableFeatures) {
         // The package info cache is the cache for package and permission information.
@@ -418,55 +138,15 @@
         mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
 
-        mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME);
-        // PackageManager.hasSystemFeature() is not used here because PackageManagerService
-        // isn't ready yet.
-        if (availableFeatures.containsKey(PackageManager.FEATURE_AUTOMOTIVE)) {
-            mPrivilegedPermissionAllowlistSourcePackageNames.add(CAR_PACKAGE_NAME);
-        }
-
-        mHandlerThread = new ServiceThread(TAG,
-                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-        Watchdog.getInstance().addThread(mHandler);
-
-        SystemConfig systemConfig = SystemConfig.getInstance();
-        mSystemPermissions = systemConfig.getSystemPermissions();
-        mGlobalGids = systemConfig.getGlobalGids();
-        mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper());
         mAttributionSourceRegistry = new AttributionSourceRegistry(context);
 
-        // propagate permission configuration
-        final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
-                SystemConfig.getInstance().getPermissions();
-        synchronized (mLock) {
-            for (int i=0; i<permConfig.size(); i++) {
-                final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
-                Permission bp = mRegistry.getPermission(perm.name);
-                if (bp == null) {
-                    bp = new Permission(perm.name, "android", Permission.TYPE_CONFIG);
-                    mRegistry.addPermission(bp);
-                }
-                if (perm.gids != null) {
-                    bp.setGids(perm.gids, perm.perUser);
-                }
-            }
-        }
-
         PermissionManagerServiceInternalImpl localService =
                 new PermissionManagerServiceInternalImpl();
         LocalServices.addService(PermissionManagerServiceInternal.class, localService);
         LocalServices.addService(PermissionManagerInternal.class, localService);
-    }
 
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
-            return;
-        }
-
-        mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
+        mPermissionManagerServiceImpl = new PermissionManagerServiceImpl(context,
+                availableFeatures);
     }
 
     /**
@@ -497,6 +177,9 @@
     }
 
     /**
+     * TODO: theianchen we want to remove this method in the future.
+     * There's a complete copy of this method in PermissionManagerServiceImpl
+     *
      * This method should typically only be used when granting or revoking
      * permissions, since the app may immediately restart after this call.
      * <p>
@@ -519,511 +202,22 @@
         }
     }
 
-    @NonNull
-    private String[] getAppOpPermissionPackagesInternal(@NonNull String permissionName) {
-        synchronized (mLock) {
-            final ArraySet<String> packageNames = mRegistry.getAppOpPermissionPackages(
-                    permissionName);
-            if (packageNames == null) {
-                return EmptyArray.STRING;
-            }
-            return packageNames.toArray(new String[0]);
-        }
-    }
-
-    @Override
-    @NonNull
-    public ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(
-            @PermissionGroupInfoFlags int flags) {
-        final int callingUid = getCallingUid();
-        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-            return ParceledListSlice.emptyList();
-        }
-
-        final List<PermissionGroupInfo> out = new ArrayList<>();
-        synchronized (mLock) {
-            for (ParsedPermissionGroup pg : mRegistry.getPermissionGroups()) {
-                out.add(PackageInfoUtils.generatePermissionGroupInfo(pg, flags));
-            }
-        }
-
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid,
-                callingUserId));
-        return new ParceledListSlice<>(out);
-    }
-
-
-    @Override
-    @Nullable
-    public PermissionGroupInfo getPermissionGroupInfo(String groupName,
-            @PermissionGroupInfoFlags int flags) {
-        final int callingUid = getCallingUid();
-        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-            return null;
-        }
-
-        final PermissionGroupInfo permissionGroupInfo;
-        synchronized (mLock) {
-            final ParsedPermissionGroup permissionGroup = mRegistry.getPermissionGroup(groupName);
-            if (permissionGroup == null) {
-                return null;
-            }
-            permissionGroupInfo = PackageInfoUtils.generatePermissionGroupInfo(permissionGroup,
-                    flags);
-        }
-
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (mPackageManagerInt.filterAppAccess(permissionGroupInfo.packageName, callingUid,
-                callingUserId)) {
-            EventLog.writeEvent(0x534e4554, "186113473", callingUid, groupName);
-            return null;
-        }
-        return permissionGroupInfo;
-    }
-
-
-    @Override
-    @Nullable
-    public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
-            @PermissionInfoFlags int flags) {
-        final int callingUid = getCallingUid();
-        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-            return null;
-        }
-
-        final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName);
-        final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage,
-                callingUid);
-        final PermissionInfo permissionInfo;
-        synchronized (mLock) {
-            final Permission bp = mRegistry.getPermission(permName);
-            if (bp == null) {
-                return null;
-            }
-            permissionInfo = bp.generatePermissionInfo(flags, targetSdkVersion);
-        }
-
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (mPackageManagerInt.filterAppAccess(permissionInfo.packageName, callingUid,
-                callingUserId)) {
-            EventLog.writeEvent(0x534e4554, "183122164", callingUid, permName);
-            return null;
-        }
-        return permissionInfo;
-    }
-
-    private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) {
-        final int appId = UserHandle.getAppId(uid);
-        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID
-                || appId == Process.SHELL_UID) {
-            // System sees all flags.
-            return Build.VERSION_CODES.CUR_DEVELOPMENT;
-        }
-        if (pkg == null) {
-            return Build.VERSION_CODES.CUR_DEVELOPMENT;
-        }
-        return pkg.getTargetSdkVersion();
-    }
-
-    @Override
-    @Nullable
-    public ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
-            @PermissionInfoFlags int flags) {
-        final int callingUid = getCallingUid();
-        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-            return null;
-        }
-
-        final List<PermissionInfo> out = new ArrayList<>(10);
-        synchronized (mLock) {
-            if (groupName != null && mRegistry.getPermissionGroup(groupName) == null) {
-                return null;
-            }
-            for (Permission bp : mRegistry.getPermissions()) {
-                if (Objects.equals(bp.getGroup(), groupName)) {
-                    out.add(bp.generatePermissionInfo(flags));
-                }
-            }
-        }
-
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid,
-                callingUserId));
-        return new ParceledListSlice<>(out);
-    }
-
-    @Override
-    public boolean addPermission(PermissionInfo info, boolean async) {
-        final int callingUid = getCallingUid();
-        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-            throw new SecurityException("Instant apps can't add permissions");
-        }
-        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
-            throw new SecurityException("Label must be specified in permission");
-        }
-        final boolean added;
-        final boolean changed;
-        synchronized (mLock) {
-            final Permission tree = mRegistry.enforcePermissionTree(info.name, callingUid);
-            Permission bp = mRegistry.getPermission(info.name);
-            added = bp == null;
-            int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
-            if (added) {
-                enforcePermissionCapLocked(info, tree);
-                bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC);
-            } else if (!bp.isDynamic()) {
-                throw new SecurityException("Not allowed to modify non-dynamic permission "
-                        + info.name);
-            }
-            changed = bp.addToTree(fixedLevel, info, tree);
-            if (added) {
-                mRegistry.addPermission(bp);
-            }
-        }
-        if (changed) {
-            mPackageManagerInt.writeSettings(async);
-        }
-        return added;
-    }
-
-    @Override
-    public void removePermission(String permName) {
-        final int callingUid = getCallingUid();
-        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-            throw new SecurityException("Instant applications don't have access to this method");
-        }
-        synchronized (mLock) {
-            mRegistry.enforcePermissionTree(permName, callingUid);
-            final Permission bp = mRegistry.getPermission(permName);
-            if (bp == null) {
-                return;
-            }
-            if (bp.isDynamic()) {
-                // TODO: switch this back to SecurityException
-                Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
-                        + permName);
-            }
-            mRegistry.removePermission(permName);
-        }
-        mPackageManagerInt.writeSettings(false);
-    }
-
-    @Override
-    public int getPermissionFlags(String packageName, String permName, int userId) {
-        final int callingUid = getCallingUid();
-        return getPermissionFlagsInternal(packageName, permName, callingUid, userId);
-    }
-
-    private int getPermissionFlagsInternal(
-            String packageName, String permName, int callingUid, int userId) {
-        if (!mUserManagerInt.exists(userId)) {
-            return 0;
-        }
-
-        enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags");
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                false, // checkShell
-                "getPermissionFlags");
-
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null) {
-            return 0;
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            return 0;
-        }
-
-        synchronized (mLock) {
-            if (mRegistry.getPermission(permName) == null) {
-                return 0;
-            }
-
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
-                return 0;
-            }
-
-            return uidState.getPermissionFlags(permName);
-        }
-    }
-
-    @Override
-    public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
-        final int callingUid = getCallingUid();
-        boolean overridePolicy = false;
-
-        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
-            final long callingIdentity = Binder.clearCallingIdentity();
-            try {
-                if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                    if (checkAdjustPolicyFlagPermission) {
-                        mContext.enforceCallingOrSelfPermission(
-                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
-                                "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
-                                        + " to change policy flags");
-                    } else if (mPackageManagerInt.getUidTargetSdkVersion(callingUid)
-                            >= Build.VERSION_CODES.Q) {
-                        throw new IllegalArgumentException(
-                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs "
-                                        + " to be checked for packages targeting "
-                                        + Build.VERSION_CODES.Q + " or later when changing policy "
-                                        + "flags");
-                    }
-                    overridePolicy = true;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(callingIdentity);
-            }
-        }
-
-        updatePermissionFlagsInternal(
-                packageName, permName, flagMask, flagValues, callingUid, userId,
-                overridePolicy, mDefaultPermissionCallback);
-    }
-
-    private void updatePermissionFlagsInternal(String packageName, String permName, int flagMask,
-            int flagValues, int callingUid, int userId, boolean overridePolicy,
-            PermissionCallback callback) {
-        if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
-                && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is updating flags for " + packageName + " "
-                            + permName + " for user " + userId  + " "
-                            + DebugUtils.flagsToString(
-                                    PackageManager.class, "FLAG_PERMISSION_", flagMask)
-                            + " := "
-                            + DebugUtils.flagsToString(
-                                    PackageManager.class, "FLAG_PERMISSION_", flagValues)
-                            + " on behalf of uid " + callingUid
-                            + " " + mPackageManagerInt.getNameForUid(callingUid),
-                    new RuntimeException());
-        }
-
-        if (!mUserManagerInt.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                "updatePermissionFlags");
-
-        if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) {
-            throw new SecurityException("updatePermissionFlags requires "
-                    + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
-        }
-
-        // Only the system can change these flags and nothing else.
-        if (callingUid != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-            flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-            flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-            flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
-        }
-
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null) {
-            Log.e(TAG, "Unknown package: " + packageName);
-            return;
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-
-        boolean isRequested = false;
-        // Fast path, the current package has requested the permission.
-        if (pkg.getRequestedPermissions().contains(permName)) {
-            isRequested = true;
-        }
-        if (!isRequested) {
-            // Slow path, go through all shared user packages.
-            String[] sharedUserPackageNames =
-                    mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId);
-            for (String sharedUserPackageName : sharedUserPackageNames) {
-                AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage(
-                        sharedUserPackageName);
-                if (sharedUserPkg != null
-                        && sharedUserPkg.getRequestedPermissions().contains(permName)) {
-                    isRequested = true;
-                    break;
-                }
-            }
-        }
-
-        final boolean isRuntimePermission;
-        final boolean permissionUpdated;
-        synchronized (mLock) {
-            final Permission bp = mRegistry.getPermission(permName);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + permName);
-            }
-
-            isRuntimePermission = bp.isRuntime();
-
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
-                return;
-            }
-
-            if (!uidState.hasPermissionState(permName) && !isRequested) {
-                Log.e(TAG, "Permission " + permName + " isn't requested by package " + packageName);
-                return;
-            }
-
-            permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues);
-        }
-
-        if (permissionUpdated && isRuntimePermission) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
-        if (permissionUpdated && callback != null) {
-            // Install and runtime permissions are stored in different places,
-            // so figure out what permission changed and persist the change.
-            if (!isRuntimePermission) {
-                int userUid = UserHandle.getUid(userId, pkg.getUid());
-                callback.onInstallPermissionUpdatedNotifyListener(userUid);
-            } else {
-                callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid());
-            }
-        }
-    }
-
-    /**
-     * Update the permission flags for all packages and runtime permissions of a user in order
-     * to allow device or profile owner to remove POLICY_FIXED.
-     */
-    @Override
-    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues,
-            final int userId) {
-        final int callingUid = getCallingUid();
-        if (!mUserManagerInt.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions(
-                "updatePermissionFlagsForAllApps");
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                "updatePermissionFlagsForAllApps");
-
-        // Only the system can change system fixed flags.
-        final int effectiveFlagMask = (callingUid != Process.SYSTEM_UID)
-                ? flagMask : flagMask & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-        final int effectiveFlagValues = (callingUid != Process.SYSTEM_UID)
-                ? flagValues : flagValues & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-
-        final boolean[] changed = new boolean[1];
-        mPackageManagerInt.forEachPackage(pkg -> {
-            synchronized (mLock) {
-                final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-                if (uidState == null) {
-                    Slog.e(TAG,
-                            "Missing permissions state for " + pkg.getPackageName() + " and user "
-                                    + userId);
-                    return;
-                }
-                changed[0] |= uidState.updatePermissionFlagsForAllPermissions(
-                        effectiveFlagMask, effectiveFlagValues);
-            }
-            mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid());
-        });
-
-        if (changed[0]) {
-            mPackageManagerInt.writePermissionSettings(new int[] { userId }, true);
-        }
-    }
-
     private int checkPermission(String pkgName, String permName, @UserIdInt int userId) {
         // Not using Objects.requireNonNull() here for compatibility reasons.
         if (pkgName == null || permName == null) {
             return PackageManager.PERMISSION_DENIED;
         }
-        if (!mUserManagerInt.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
 
         final CheckPermissionDelegate checkPermissionDelegate;
         synchronized (mLock) {
             checkPermissionDelegate = mCheckPermissionDelegate;
         }
+
         if (checkPermissionDelegate == null) {
-            return checkPermissionImpl(pkgName, permName, userId);
+            return mPermissionManagerServiceImpl.checkPermission(pkgName, permName, userId);
         }
         return checkPermissionDelegate.checkPermission(pkgName, permName, userId,
-                this::checkPermissionImpl);
-    }
-
-    private int checkPermissionImpl(String pkgName, String permName, int userId) {
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName);
-        if (pkg == null) {
-            return PackageManager.PERMISSION_DENIED;
-        }
-        return checkPermissionInternal(pkg, true, permName, userId);
-    }
-
-    private int checkPermissionInternal(@NonNull AndroidPackage pkg, boolean isPackageExplicit,
-            @NonNull String permissionName, @UserIdInt int userId) {
-        final int callingUid = getCallingUid();
-        if (isPackageExplicit || pkg.getSharedUserId() == null) {
-            if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-                return PackageManager.PERMISSION_DENIED;
-            }
-        } else {
-            if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
-                return PackageManager.PERMISSION_DENIED;
-            }
-        }
-
-        final int uid = UserHandle.getUid(userId, pkg.getUid());
-        final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null;
-
-        synchronized (mLock) {
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
-                        + userId);
-                return PackageManager.PERMISSION_DENIED;
-            }
-
-            if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) {
-                return PackageManager.PERMISSION_GRANTED;
-            }
-
-            final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
-            if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState,
-                    fullerPermissionName, isInstantApp)) {
-                return PackageManager.PERMISSION_GRANTED;
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
-    }
-
-    @GuardedBy("mLock")
-    private boolean checkSinglePermissionInternalLocked(@NonNull UidPermissionState uidState,
-            @NonNull String permissionName, boolean isInstantApp) {
-        if (!uidState.isPermissionGranted(permissionName)) {
-            return false;
-        }
-
-        if (isInstantApp) {
-            final Permission permission = mRegistry.getPermission(permissionName);
-            return permission != null && permission.isInstant();
-        }
-
-        return true;
+                mPermissionManagerServiceImpl::checkPermission);
     }
 
     private int checkUidPermission(int uid, String permName) {
@@ -1031,332 +225,17 @@
         if (permName == null) {
             return PackageManager.PERMISSION_DENIED;
         }
-        final int userId = UserHandle.getUserId(uid);
-        if (!mUserManagerInt.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
 
         final CheckPermissionDelegate checkPermissionDelegate;
         synchronized (mLock) {
             checkPermissionDelegate = mCheckPermissionDelegate;
         }
+
         if (checkPermissionDelegate == null)  {
-            return checkUidPermissionImpl(uid, permName);
+            return mPermissionManagerServiceImpl.checkUidPermission(uid, permName);
         }
         return checkPermissionDelegate.checkUidPermission(uid, permName,
-                this::checkUidPermissionImpl);
-    }
-
-    private int checkUidPermissionImpl(int uid, String permName) {
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
-        return checkUidPermissionInternal(pkg, uid, permName);
-    }
-
-    /**
-     * Checks whether or not the given package has been granted the specified
-     * permission. If the given package is {@code null}, we instead check the
-     * system permissions for the given UID.
-     *
-     * @see SystemConfig#getSystemPermissions()
-     */
-    private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid,
-            @NonNull String permissionName) {
-        if (pkg != null) {
-            final int userId = UserHandle.getUserId(uid);
-            return checkPermissionInternal(pkg, false, permissionName, userId);
-        }
-
-        synchronized (mLock) {
-            if (checkSingleUidPermissionInternalLocked(uid, permissionName)) {
-                return PackageManager.PERMISSION_GRANTED;
-            }
-
-            final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
-            if (fullerPermissionName != null
-                    && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) {
-                return PackageManager.PERMISSION_GRANTED;
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
-    }
-
-    @GuardedBy("mLock")
-    private boolean checkSingleUidPermissionInternalLocked(int uid,
-            @NonNull String permissionName) {
-        ArraySet<String> permissions = mSystemPermissions.get(uid);
-        return permissions != null && permissions.contains(permissionName);
-    }
-
-    @Override
-    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
-        mContext.enforceCallingOrSelfPermission(
-                Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
-                "addOnPermissionsChangeListener");
-
-        mOnPermissionChangeListeners.addListener(listener);
-    }
-
-    @Override
-    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
-        if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant applications don't have access to this method");
-        }
-        mOnPermissionChangeListeners.removeListener(listener);
-    }
-
-    @Nullable
-    @Override
-    public List<String> getAllowlistedRestrictedPermissions(@NonNull String packageName,
-            @PermissionWhitelistFlags int flags, @UserIdInt int userId) {
-        Objects.requireNonNull(packageName);
-        Preconditions.checkFlagsArgument(flags,
-                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
-        Preconditions.checkArgumentNonNegative(userId, null);
-
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS,
-                    "getAllowlistedRestrictedPermissions for user " + userId);
-        }
-
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null) {
-            return null;
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) {
-            return null;
-        }
-        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
-                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
-                        == PackageManager.PERMISSION_GRANTED;
-        final boolean isCallerInstallerOnRecord =
-                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
-
-        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
-                && !isCallerPrivileged) {
-            throw new SecurityException("Querying system allowlist requires "
-                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-        }
-
-        if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
-                | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
-            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                throw new SecurityException("Querying upgrade or installer allowlist"
-                        + " requires being installer on record or "
-                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-            }
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            return getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    @Nullable
-    private List<String> getAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
-            @PermissionWhitelistFlags int flags, @UserIdInt int userId) {
-        synchronized (mLock) {
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
-                        + userId);
-                return null;
-            }
-
-            int queryFlags = 0;
-            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
-                queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-            }
-            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
-                queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-            }
-            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
-                queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-            }
-
-            ArrayList<String> allowlistedPermissions = null;
-
-            final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
-            for (int i = 0; i < permissionCount; i++) {
-                final String permissionName = pkg.getRequestedPermissions().get(i);
-                final int currentFlags =
-                        uidState.getPermissionFlags(permissionName);
-                if ((currentFlags & queryFlags) != 0) {
-                    if (allowlistedPermissions == null) {
-                        allowlistedPermissions = new ArrayList<>();
-                    }
-                    allowlistedPermissions.add(permissionName);
-                }
-            }
-
-            return allowlistedPermissions;
-        }
-    }
-
-    @Override
-    public boolean addAllowlistedRestrictedPermission(@NonNull String packageName,
-            @NonNull String permName, @PermissionWhitelistFlags int flags,
-            @UserIdInt int userId) {
-        // Other argument checks are done in get/setAllowlistedRestrictedPermissions
-        Objects.requireNonNull(permName);
-
-        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
-            return false;
-        }
-
-        List<String> permissions =
-                getAllowlistedRestrictedPermissions(packageName, flags, userId);
-        if (permissions == null) {
-            permissions = new ArrayList<>(1);
-        }
-        if (permissions.indexOf(permName) < 0) {
-            permissions.add(permName);
-            return setAllowlistedRestrictedPermissions(packageName, permissions,
-                    flags, userId);
-        }
-        return false;
-    }
-
-    private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
-            @NonNull String permName) {
-        final String permissionPackageName;
-        final boolean isImmutablyRestrictedPermission;
-        synchronized (mLock) {
-            final Permission bp = mRegistry.getPermission(permName);
-            if (bp == null) {
-                Slog.w(TAG, "No such permissions: " + permName);
-                return false;
-            }
-            permissionPackageName = bp.getPackageName();
-            isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted()
-                    && bp.isImmutablyRestricted();
-        }
-
-        final int callingUid = getCallingUid();
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) {
-            EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName);
-            return false;
-        }
-
-        if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission(
-                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("Cannot modify allowlisting of an immutably "
-                    + "restricted permission: " + permName);
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName,
-            @NonNull String permName, @PermissionWhitelistFlags int flags,
-            @UserIdInt int userId) {
-        // Other argument checks are done in get/setAllowlistedRestrictedPermissions
-        Objects.requireNonNull(permName);
-
-        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
-            return false;
-        }
-
-        final List<String> permissions =
-                getAllowlistedRestrictedPermissions(packageName, flags, userId);
-        if (permissions != null && permissions.remove(permName)) {
-            return setAllowlistedRestrictedPermissions(packageName, permissions,
-                    flags, userId);
-        }
-        return false;
-    }
-
-    private boolean setAllowlistedRestrictedPermissions(@NonNull String packageName,
-            @Nullable List<String> permissions, @PermissionWhitelistFlags int flags,
-            @UserIdInt int userId) {
-        Objects.requireNonNull(packageName);
-        Preconditions.checkFlagsArgument(flags,
-                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
-                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
-        Preconditions.checkArgument(Integer.bitCount(flags) == 1);
-        Preconditions.checkArgumentNonNegative(userId, null);
-
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingOrSelfPermission(
-                    Manifest.permission.INTERACT_ACROSS_USERS,
-                    "setAllowlistedRestrictedPermissions for user " + userId);
-        }
-
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null) {
-            return false;
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) {
-            return false;
-        }
-
-        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
-                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
-                        == PackageManager.PERMISSION_GRANTED;
-        final boolean isCallerInstallerOnRecord =
-                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
-
-        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) {
-            throw new SecurityException("Modifying system allowlist requires "
-                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-        }
-
-        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
-            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                throw new SecurityException("Modifying upgrade allowlist requires"
-                        + " being installer on record or "
-                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-            }
-            final List<String> allowlistedPermissions =
-                    getAllowlistedRestrictedPermissions(pkg.getPackageName(), flags, userId);
-            if (permissions == null || permissions.isEmpty()) {
-                if (allowlistedPermissions == null || allowlistedPermissions.isEmpty()) {
-                    return true;
-                }
-            } else {
-                // Only the system can add and remove while the installer can only remove.
-                final int permissionCount = permissions.size();
-                for (int i = 0; i < permissionCount; i++) {
-                    if ((allowlistedPermissions == null
-                            || !allowlistedPermissions.contains(permissions.get(i)))
-                            && !isCallerPrivileged) {
-                        throw new SecurityException("Adding to upgrade allowlist requires"
-                                + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                    }
-                }
-            }
-
-            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
-                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                    throw new SecurityException("Modifying installer allowlist requires"
-                            + " being installer on record or "
-                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                }
-            }
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        return true;
+                mPermissionManagerServiceImpl::checkUidPermission);
     }
 
     @Override
@@ -1438,744 +317,6 @@
         }
     }
 
-    @Override
-    public void grantRuntimePermission(String packageName, String permName, final int userId) {
-        final int callingUid = Binder.getCallingUid();
-        final boolean overridePolicy =
-                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
-                        == PackageManager.PERMISSION_GRANTED;
-
-        grantRuntimePermissionInternal(packageName, permName, overridePolicy,
-                callingUid, userId, mDefaultPermissionCallback);
-    }
-
-    private void grantRuntimePermissionInternal(String packageName, String permName,
-            boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
-        if (PermissionManager.DEBUG_TRACE_GRANTS
-                && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " "
-                    + permName + " for user " + userId + " on behalf of uid " + callingUid
-                    + " " + mPackageManagerInt.getNameForUid(callingUid),
-                    new RuntimeException());
-        }
-        if (!mUserManagerInt.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
-                "grantRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                "grantRuntimePermission");
-
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName);
-        if (pkg == null || ps == null) {
-            Log.e(TAG, "Unknown package: " + packageName);
-            return;
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-
-        final boolean isRolePermission;
-        final boolean isSoftRestrictedPermission;
-        synchronized (mLock) {
-            final Permission permission = mRegistry.getPermission(permName);
-            if (permission == null) {
-                throw new IllegalArgumentException("Unknown permission: " + permName);
-            }
-            isRolePermission = permission.isRole();
-            isSoftRestrictedPermission = permission.isSoftRestricted();
-        }
-        final boolean mayGrantRolePermission = isRolePermission
-                && mayManageRolePermission(callingUid);
-        final boolean mayGrantSoftRestrictedPermission = isSoftRestrictedPermission
-                && SoftRestrictedPermissionPolicy.forPermission(mContext,
-                        AndroidPackageUtils.generateAppInfoWithoutState(pkg), pkg,
-                        UserHandle.of(userId), permName)
-                        .mayGrantPermission();
-
-        final boolean isRuntimePermission;
-        final boolean permissionHasGids;
-        synchronized (mLock) {
-            final Permission bp = mRegistry.getPermission(permName);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + permName);
-            }
-
-            isRuntimePermission = bp.isRuntime();
-            permissionHasGids = bp.hasGids();
-            if (isRuntimePermission || bp.isDevelopment()) {
-                // Good.
-            } else if (bp.isRole()) {
-                if (!mayGrantRolePermission) {
-                    throw new SecurityException("Permission " + permName + " is managed by role");
-                }
-            } else {
-                throw new SecurityException("Permission " + permName + " requested by "
-                        + pkg.getPackageName() + " is not a changeable permission type");
-            }
-
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
-                        + userId);
-                return;
-            }
-
-            if (!(uidState.hasPermissionState(permName)
-                    || pkg.getRequestedPermissions().contains(permName))) {
-                throw new SecurityException("Package " + pkg.getPackageName()
-                        + " has not requested permission " + permName);
-            }
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) {
-                return;
-            }
-
-            final int flags = uidState.getPermissionFlags(permName);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                Log.e(TAG, "Cannot grant system fixed permission "
-                        + permName + " for package " + packageName);
-                return;
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                Log.e(TAG, "Cannot grant policy fixed permission "
-                        + permName + " for package " + packageName);
-                return;
-            }
-
-            if (bp.isHardRestricted()
-                    && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
-                Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
-                        + permName + " for package " + packageName);
-                return;
-            }
-
-            if (bp.isSoftRestricted() && !mayGrantSoftRestrictedPermission) {
-                Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
-                        + packageName);
-                return;
-            }
-
-            if (bp.isDevelopment() || bp.isRole()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                // TODO(zhanghai): We are breaking the behavior above by making all permission state
-                //  per-user. It isn't documented behavior and relatively rarely used anyway.
-                if (!uidState.grantPermission(bp)) {
-                    return;
-                }
-            } else {
-                if (ps.getUserStateOrDefault(userId).isInstantApp() && !bp.isInstant()) {
-                    throw new SecurityException("Cannot grant non-ephemeral permission" + permName
-                            + " for package " + packageName);
-                }
-
-                if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
-                    Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
-                    return;
-                }
-
-                if (!uidState.grantPermission(bp)) {
-                    return;
-                }
-            }
-        }
-
-        if (isRuntimePermission) {
-            logPermission(MetricsEvent.ACTION_PERMISSION_GRANTED, permName, packageName);
-        }
-
-        final int uid = UserHandle.getUid(userId, pkg.getUid());
-        if (callback != null) {
-            if (isRuntimePermission) {
-                callback.onPermissionGranted(uid, userId);
-            } else {
-                callback.onInstallPermissionGranted();
-            }
-            if (permissionHasGids) {
-                callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
-            }
-        }
-
-        if (isRuntimePermission) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
-    }
-
-    @Override
-    public void revokeRuntimePermission(String packageName, String permName, int userId,
-            String reason) {
-        final int callingUid = Binder.getCallingUid();
-        final boolean overridePolicy =
-                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
-                        == PackageManager.PERMISSION_GRANTED;
-
-        revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId,
-                reason, mDefaultPermissionCallback);
-    }
-
-    private void revokeRuntimePermissionInternal(String packageName, String permName,
-            boolean overridePolicy, int callingUid, final int userId, String reason,
-            PermissionCallback callback) {
-        if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
-                && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
-            Log.i(TAG, "System is revoking " + packageName + " "
-                            + permName + " for user " + userId + " on behalf of uid " + callingUid
-                            + " " + mPackageManagerInt.getNameForUid(callingUid),
-                    new RuntimeException());
-        }
-        if (!mUserManagerInt.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
-                "revokeRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true,  // requireFullPermission
-                true,  // checkShell
-                "revokeRuntimePermission");
-
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null) {
-            Log.e(TAG, "Unknown package: " + packageName);
-            return;
-        }
-        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown package: " + packageName);
-        }
-
-        final boolean isRolePermission;
-        synchronized (mLock) {
-            final Permission permission = mRegistry.getPermission(permName);
-            if (permission == null) {
-                throw new IllegalArgumentException("Unknown permission: " + permName);
-            }
-            isRolePermission = permission.isRole();
-        }
-        final boolean mayRevokeRolePermission = isRolePermission
-                // Allow ourselves to revoke role permissions due to definition changes.
-                && (callingUid == Process.myUid() || mayManageRolePermission(callingUid));
-
-        final boolean isRuntimePermission;
-        synchronized (mLock) {
-            final Permission bp = mRegistry.getPermission(permName);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + permName);
-            }
-
-            isRuntimePermission = bp.isRuntime();
-            if (isRuntimePermission || bp.isDevelopment()) {
-                // Good.
-            } else if (bp.isRole()) {
-                if (!mayRevokeRolePermission) {
-                    throw new SecurityException("Permission " + permName + " is managed by role");
-                }
-            } else {
-                throw new SecurityException("Permission " + permName + " requested by "
-                        + pkg.getPackageName() + " is not a changeable permission type");
-            }
-
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
-                        + userId);
-                return;
-            }
-
-            if (!(uidState.hasPermissionState(permName)
-                    || pkg.getRequestedPermissions().contains(permName))) {
-                throw new SecurityException("Package " + pkg.getPackageName()
-                        + " has not requested permission " + permName);
-            }
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) {
-                return;
-            }
-
-            final int flags = uidState.getPermissionFlags(permName);
-            // Only the system may revoke SYSTEM_FIXED permissions.
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                    && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
-                throw new SecurityException("Non-System UID cannot revoke system fixed permission "
-                        + permName + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke policy fixed permission "
-                        + permName + " for package " + packageName);
-            }
-
-            // Development permissions must be handled specially, since they are not
-            // normal runtime permissions.  For now they apply to all users.
-            // TODO(zhanghai): We are breaking the behavior above by making all permission state
-            //  per-user. It isn't documented behavior and relatively rarely used anyway.
-            if (!uidState.revokePermission(bp)) {
-                return;
-            }
-        }
-
-        if (isRuntimePermission) {
-            logPermission(MetricsEvent.ACTION_PERMISSION_REVOKED, permName, packageName);
-        }
-
-        if (callback != null) {
-            if (isRuntimePermission) {
-                callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId,
-                        reason);
-            } else {
-                mDefaultPermissionCallback.onInstallPermissionRevoked();
-            }
-        }
-
-        if (isRuntimePermission) {
-            notifyRuntimePermissionStateChanged(packageName, userId);
-        }
-    }
-
-    private boolean mayManageRolePermission(int uid) {
-        final PackageManager packageManager = mContext.getPackageManager();
-        final String[] packageNames = packageManager.getPackagesForUid(uid);
-        if (packageNames == null) {
-            return false;
-        }
-        final String permissionControllerPackageName =
-                packageManager.getPermissionControllerPackageName();
-        return Arrays.asList(packageNames).contains(permissionControllerPackageName);
-    }
-
-    /**
-     * Reverts user permission state changes (permissions and flags).
-     *
-     * @param pkg The package for which to reset.
-     * @param userId The device user for which to do a reset.
-     */
-    private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg,
-            @UserIdInt int userId) {
-        final String packageName = pkg.getPackageName();
-
-        // These are flags that can change base on user actions.
-        final int userSettableMask = FLAG_PERMISSION_USER_SET
-                | FLAG_PERMISSION_USER_FIXED
-                | FLAG_PERMISSION_REVOKED_COMPAT
-                | FLAG_PERMISSION_REVIEW_REQUIRED
-                | FLAG_PERMISSION_ONE_TIME
-                | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
-
-        final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
-                | FLAG_PERMISSION_POLICY_FIXED;
-
-        // Delay and combine non-async permission callbacks
-        final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
-        final boolean[] permissionRemoved = new boolean[1];
-        final ArraySet<Long> revokedPermissions = new ArraySet<>();
-        final IntArray syncUpdatedUsers = new IntArray(permissionCount);
-        final IntArray asyncUpdatedUsers = new IntArray(permissionCount);
-
-        PermissionCallback delayingPermCallback = new PermissionCallback() {
-            public void onGidsChanged(int appId, int userId) {
-                mDefaultPermissionCallback.onGidsChanged(appId, userId);
-            }
-
-            public void onPermissionChanged() {
-                mDefaultPermissionCallback.onPermissionChanged();
-            }
-
-            public void onPermissionGranted(int uid, int userId) {
-                mDefaultPermissionCallback.onPermissionGranted(uid, userId);
-            }
-
-            public void onInstallPermissionGranted() {
-                mDefaultPermissionCallback.onInstallPermissionGranted();
-            }
-
-            public void onPermissionRevoked(int uid, int userId, String reason) {
-                revokedPermissions.add(IntPair.of(uid, userId));
-
-                syncUpdatedUsers.add(userId);
-            }
-
-            public void onInstallPermissionRevoked() {
-                mDefaultPermissionCallback.onInstallPermissionRevoked();
-            }
-
-            public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
-                for (int userId : updatedUserIds) {
-                    if (sync) {
-                        syncUpdatedUsers.add(userId);
-                        asyncUpdatedUsers.remove(userId);
-                    } else {
-                        // Don't override sync=true by sync=false
-                        if (syncUpdatedUsers.indexOf(userId) == -1) {
-                            asyncUpdatedUsers.add(userId);
-                        }
-                    }
-                }
-            }
-
-            public void onPermissionRemoved() {
-                permissionRemoved[0] = true;
-            }
-
-            public void onInstallPermissionUpdated() {
-                mDefaultPermissionCallback.onInstallPermissionUpdated();
-            }
-
-            public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds,
-                    boolean sync, int uid) {
-                onPermissionUpdated(updatedUserIds, sync);
-                mOnPermissionChangeListeners.onPermissionsChanged(uid);
-            }
-
-            public void onInstallPermissionUpdatedNotifyListener(int uid) {
-                mDefaultPermissionCallback.onInstallPermissionUpdatedNotifyListener(uid);
-            }
-        };
-
-        for (int i = 0; i < permissionCount; i++) {
-            final String permName = pkg.getRequestedPermissions().get(i);
-
-            final boolean isRuntimePermission;
-            synchronized (mLock) {
-                final Permission permission = mRegistry.getPermission(permName);
-                if (permission == null) {
-                    continue;
-                }
-
-                if (permission.isRemoved()) {
-                    continue;
-                }
-                isRuntimePermission = permission.isRuntime();
-            }
-
-            // If shared user we just reset the state to which only this app contributed.
-            final String[] pkgNames = mPackageManagerInt.getSharedUserPackagesForPackage(
-                    pkg.getPackageName(), userId);
-            if (pkgNames.length > 0) {
-                boolean used = false;
-                for (String sharedPkgName : pkgNames) {
-                    final AndroidPackage sharedPkg =
-                            mPackageManagerInt.getPackage(sharedPkgName);
-                    if (sharedPkg != null && !sharedPkg.getPackageName().equals(packageName)
-                            && sharedPkg.getRequestedPermissions().contains(permName)) {
-                        used = true;
-                        break;
-                    }
-                }
-                if (used) {
-                    continue;
-                }
-            }
-
-            final int oldFlags =
-                    getPermissionFlagsInternal(packageName, permName, Process.SYSTEM_UID, userId);
-
-            // Always clear the user settable flags.
-            // If permission review is enabled and this is a legacy app, mark the
-            // permission as requiring a review as this is the initial state.
-            final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
-            final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid);
-            final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission)
-                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT
-                    : 0;
-
-            updatePermissionFlagsInternal(
-                    packageName, permName, userSettableMask, flags, Process.SYSTEM_UID, userId,
-                    false, delayingPermCallback);
-
-            // Below is only runtime permission handling.
-            if (!isRuntimePermission) {
-                continue;
-            }
-
-            // Never clobber system or policy.
-            if ((oldFlags & policyOrSystemFlags) != 0) {
-                continue;
-            }
-
-            // If this permission was granted by default or role, make sure it is.
-            if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
-                    || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) {
-                // PermissionPolicyService will handle the app op for runtime permissions later.
-                grantRuntimePermissionInternal(packageName, permName, false,
-                        Process.SYSTEM_UID, userId, delayingPermCallback);
-            // In certain cases we should leave the state unchanged:
-            // -- If permission review is enabled the permissions for a legacy apps
-            // are represented as constantly granted runtime ones
-            // -- If the permission was split from a non-runtime permission
-            } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0
-                    && !isPermissionSplitFromNonRuntime(permName, targetSdk)) {
-                // Otherwise, reset the permission.
-                revokeRuntimePermissionInternal(packageName, permName, false, Process.SYSTEM_UID,
-                        userId, null, delayingPermCallback);
-            }
-        }
-
-        // Execute delayed callbacks
-        if (permissionRemoved[0]) {
-            mDefaultPermissionCallback.onPermissionRemoved();
-        }
-
-        // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot
-        // kill uid while holding mPackages-lock
-        if (!revokedPermissions.isEmpty()) {
-            int numRevokedPermissions = revokedPermissions.size();
-            for (int i = 0; i < numRevokedPermissions; i++) {
-                int revocationUID = IntPair.first(revokedPermissions.valueAt(i));
-                int revocationUserId = IntPair.second(revokedPermissions.valueAt(i));
-
-                mOnPermissionChangeListeners.onPermissionsChanged(revocationUID);
-
-                // Kill app later as we are holding mPackages
-                mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId,
-                        KILL_APP_REASON_PERMISSIONS_REVOKED));
-            }
-        }
-
-        mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false);
-        mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true);
-    }
-
-    /**
-     * Determine if the given permission should be treated as split from a
-     * non-runtime permission for an application targeting the given SDK level.
-     */
-    private boolean isPermissionSplitFromNonRuntime(String permName, int targetSdk) {
-        final List<PermissionManager.SplitPermissionInfo> splitPerms = getSplitPermissionInfos();
-        final int size = splitPerms.size();
-        for (int i = 0; i < size; i++) {
-            final PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i);
-            if (targetSdk < splitPerm.getTargetSdk()
-                    && splitPerm.getNewPermissions().contains(permName)) {
-                synchronized (mLock) {
-                    final Permission perm =
-                            mRegistry.getPermission(splitPerm.getSplitPermission());
-                    return perm != null && !perm.isRuntime();
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * This change makes it so that apps are told to show rationale for asking for background
-     * location access every time they request.
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
-    private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
-
-    @Override
-    public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            @UserIdInt int userId) {
-        final int callingUid = Binder.getCallingUid();
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "canShowRequestPermissionRationale for user " + userId);
-        }
-
-        final int uid =
-                mPackageManagerInt.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
-        if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(uid)) {
-            return false;
-        }
-
-        if (checkPermission(packageName, permName, userId)
-                == PackageManager.PERMISSION_GRANTED) {
-            return false;
-        }
-
-        final int flags;
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-
-        final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                | PackageManager.FLAG_PERMISSION_POLICY_FIXED
-                | PackageManager.FLAG_PERMISSION_USER_FIXED;
-
-        if ((flags & fixedFlags) != 0) {
-            return false;
-        }
-
-        synchronized (mLock) {
-            final Permission permission = mRegistry.getPermission(permName);
-            if (permission == null) {
-                return false;
-            }
-            if (permission.isHardRestricted()
-                    && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
-                return false;
-            }
-        }
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
-                    && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
-                    packageName, userId)) {
-                return true;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-
-        return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
-    }
-
-    @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
-        if (UserHandle.getCallingUserId() != userId) {
-            mContext.enforceCallingPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "isPermissionRevokedByPolicy for user " + userId);
-        }
-
-        if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) {
-            return false;
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        if (mPackageManagerInt.filterAppAccess(packageName, callingUid, userId)) {
-            return false;
-        }
-
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            final int flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId);
-            return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
-    }
-
-    /**
-     * Get the state of the runtime permissions as xml file.
-     *
-     * <p>Can not be called on main thread.
-     *
-     * @param userId The user ID the data should be extracted for
-     *
-     * @return The state as a xml file
-     */
-    @Nullable
-    private byte[] backupRuntimePermissions(@UserIdInt int userId) {
-        CompletableFuture<byte[]> backup = new CompletableFuture<>();
-        mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
-                mContext.getMainExecutor(), backup::complete);
-
-        try {
-            return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException | ExecutionException  | TimeoutException e) {
-            Slog.e(TAG, "Cannot create permission backup for user " + userId, e);
-            return null;
-        }
-    }
-
-    /**
-     * Restore a permission state previously backed up via {@link #backupRuntimePermissions}.
-     *
-     * <p>If not all state can be restored, the un-appliable state will be delayed and can be
-     * applied via {@link #restoreDelayedRuntimePermissions}.
-     *
-     * @param backup The state as an xml file
-     * @param userId The user ID the data should be restored for
-     */
-    private void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) {
-        synchronized (mLock) {
-            mHasNoDelayedPermBackup.delete(userId);
-        }
-        mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup,
-                UserHandle.of(userId));
-    }
-
-    /**
-     * Try to apply permission backup that was previously not applied.
-     *
-     * <p>Can not be called on main thread.
-     *
-     * @param packageName The package that is newly installed
-     * @param userId The user ID the package is installed for
-     *
-     * @see #restoreRuntimePermissions
-     */
-    private void restoreDelayedRuntimePermissions(@NonNull String packageName,
-            @UserIdInt int userId) {
-        synchronized (mLock) {
-            if (mHasNoDelayedPermBackup.get(userId, false)) {
-                return;
-            }
-        }
-        mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName,
-                UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> {
-                    if (hasMoreBackup) {
-                        return;
-                    }
-                    synchronized (mLock) {
-                        mHasNoDelayedPermBackup.put(userId, true);
-                    }
-                });
-    }
-
-    private void addOnRuntimePermissionStateChangedListener(@NonNull
-            OnRuntimePermissionStateChangedListener listener) {
-        synchronized (mLock) {
-            mRuntimePermissionStateChangedListeners.add(listener);
-        }
-    }
-
-    private void removeOnRuntimePermissionStateChangedListener(@NonNull
-            OnRuntimePermissionStateChangedListener listener) {
-        synchronized (mLock) {
-            mRuntimePermissionStateChangedListeners.remove(listener);
-        }
-    }
-
-    private void notifyRuntimePermissionStateChanged(@NonNull String packageName,
-            @UserIdInt int userId) {
-        FgThread.getHandler().sendMessage(PooledLambda.obtainMessage
-                (PermissionManagerService::doNotifyRuntimePermissionStateChanged,
-                        PermissionManagerService.this, packageName, userId));
-    }
-
-    private void doNotifyRuntimePermissionStateChanged(@NonNull String packageName,
-            @UserIdInt int userId) {
-        final ArrayList<OnRuntimePermissionStateChangedListener> listeners;
-        synchronized (mLock) {
-            if (mRuntimePermissionStateChangedListeners.isEmpty()) {
-                return;
-            }
-            listeners = new ArrayList<>(mRuntimePermissionStateChangedListeners);
-        }
-        final int listenerCount = listeners.size();
-        for (int i = 0; i < listenerCount; i++) {
-            listeners.get(i).onRuntimePermissionStateChanged(packageName, userId);
-        }
-    }
-
     private void startShellPermissionIdentityDelegationInternal(int uid,
             @NonNull String packageName, @Nullable List<String> permissionNames) {
         synchronized (mLock) {
@@ -2212,1150 +353,6 @@
         mCheckPermissionDelegate = delegate;
     }
 
-    /**
-     * If the app is updated, and has scoped storage permissions, then it is possible that the
-     * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
-     * @param newPackage The new package that was installed
-     * @param oldPackage The old package that was updated
-     */
-    private void revokeStoragePermissionsIfScopeExpandedInternal(
-            @NonNull AndroidPackage newPackage,
-            @NonNull AndroidPackage oldPackage) {
-        boolean downgradedSdk = oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q
-                && newPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q;
-        boolean upgradedSdk = oldPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q
-                && newPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q;
-        boolean newlyRequestsLegacy = !upgradedSdk && !oldPackage.isRequestLegacyExternalStorage()
-                && newPackage.isRequestLegacyExternalStorage();
-
-        if (!newlyRequestsLegacy && !downgradedSdk) {
-            return;
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        for (int userId: getAllUserIds()) {
-            int numRequestedPermissions = newPackage.getRequestedPermissions().size();
-            for (int i = 0; i < numRequestedPermissions; i++) {
-                PermissionInfo permInfo = getPermissionInfo(
-                        newPackage.getRequestedPermissions().get(i),
-                        newPackage.getPackageName(), 0);
-                if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
-                    continue;
-                }
-
-                EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
-                        "Revoking permission " + permInfo.name + " from package "
-                                + newPackage.getPackageName() + " as either the sdk downgraded "
-                                + downgradedSdk + " or newly requested legacy full storage "
-                                + newlyRequestsLegacy);
-
-                try {
-                    revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name,
-                            false, callingUid, userId, null, mDefaultPermissionCallback);
-                } catch (IllegalStateException | SecurityException e) {
-                    Log.e(TAG, "unable to revoke " + permInfo.name + " for "
-                            + newPackage.getPackageName() + " user " + userId, e);
-                }
-            }
-        }
-
-    }
-
-    /**
-     * We might auto-grant permissions if any permission of the group is already granted. Hence if
-     * the group of a granted permission changes we need to revoke it to avoid having permissions of
-     * the new group auto-granted.
-     *
-     * @param newPackage The new package that was installed
-     * @param oldPackage The old package that was updated
-     */
-    private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage,
-            @NonNull AndroidPackage oldPackage) {
-        final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions());
-        final ArrayMap<String, String> oldPermissionNameToGroupName
-                = new ArrayMap<>(numOldPackagePermissions);
-
-        for (int i = 0; i < numOldPackagePermissions; i++) {
-            final ParsedPermission permission = oldPackage.getPermissions().get(i);
-
-            if (permission.getParsedPermissionGroup() != null) {
-                oldPermissionNameToGroupName.put(permission.getName(),
-                        permission.getParsedPermissionGroup().getName());
-            }
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        final int numNewPackagePermissions = ArrayUtils.size(newPackage.getPermissions());
-        for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions;
-                newPermissionNum++) {
-            final ParsedPermission newPermission =
-                    newPackage.getPermissions().get(newPermissionNum);
-            final int newProtection = ParsedPermissionUtils.getProtection(newPermission);
-
-            if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
-                final String permissionName = newPermission.getName();
-                final String newPermissionGroupName =
-                        newPermission.getParsedPermissionGroup() == null
-                                ? null : newPermission.getParsedPermissionGroup().getName();
-                final String oldPermissionGroupName = oldPermissionNameToGroupName.get(
-                        permissionName);
-
-                if (newPermissionGroupName != null
-                        && !newPermissionGroupName.equals(oldPermissionGroupName)) {
-                    final int[] userIds = mUserManagerInt.getUserIds();
-                    mPackageManagerInt.forEachPackage(pkg -> {
-                        final String packageName = pkg.getPackageName();
-                        for (final int userId : userIds) {
-                            final int permissionState = checkPermission(packageName, permissionName,
-                                    userId);
-                            if (permissionState == PackageManager.PERMISSION_GRANTED) {
-                                EventLog.writeEvent(0x534e4554, "72710897",
-                                        newPackage.getUid(),
-                                        "Revoking permission " + permissionName +
-                                        " from package " + packageName +
-                                        " as the group changed from " + oldPermissionGroupName +
-                                        " to " + newPermissionGroupName);
-
-                                try {
-                                    revokeRuntimePermissionInternal(packageName, permissionName,
-                                            false, callingUid, userId, null,
-                                            mDefaultPermissionCallback);
-                                } catch (IllegalArgumentException e) {
-                                    Slog.e(TAG, "Could not revoke " + permissionName + " from "
-                                            + packageName, e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
-        }
-    }
-
-    /**
-     * If permissions are upgraded to runtime, or their owner changes to the system, then any
-     * granted permissions must be revoked.
-     *
-     * @param permissionsToRevoke A list of permission names to revoke
-     */
-    private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
-            @NonNull List<String> permissionsToRevoke) {
-        final int[] userIds = mUserManagerInt.getUserIds();
-        final int numPermissions = permissionsToRevoke.size();
-        final int callingUid = Binder.getCallingUid();
-
-        for (int permNum = 0; permNum < numPermissions; permNum++) {
-            final String permName = permissionsToRevoke.get(permNum);
-            final boolean isInternalPermission;
-            synchronized (mLock) {
-                final Permission bp = mRegistry.getPermission(permName);
-                if (bp == null || !(bp.isInternal() || bp.isRuntime())) {
-                    continue;
-                }
-                isInternalPermission = bp.isInternal();
-            }
-            mPackageManagerInt.forEachPackage(pkg -> {
-                final String packageName = pkg.getPackageName();
-                final int appId = pkg.getUid();
-                if (appId < Process.FIRST_APPLICATION_UID) {
-                    // do not revoke from system apps
-                    return;
-                }
-                for (final int userId : userIds) {
-                    final int permissionState = checkPermissionImpl(packageName, permName,
-                            userId);
-                    final int flags = getPermissionFlags(packageName, permName, userId);
-                    final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
-                            | FLAG_PERMISSION_POLICY_FIXED
-                            | FLAG_PERMISSION_GRANTED_BY_DEFAULT
-                            | FLAG_PERMISSION_GRANTED_BY_ROLE;
-                    if (permissionState == PackageManager.PERMISSION_GRANTED
-                            && (flags & flagMask) == 0) {
-                        final int uid = UserHandle.getUid(userId, appId);
-                        if (isInternalPermission) {
-                            EventLog.writeEvent(0x534e4554, "195338390", uid,
-                                    "Revoking permission " + permName + " from package "
-                                            + packageName + " due to definition change");
-                        } else {
-                            EventLog.writeEvent(0x534e4554, "154505240", uid,
-                                    "Revoking permission " + permName + " from package "
-                                            + packageName + " due to definition change");
-                            EventLog.writeEvent(0x534e4554, "168319670", uid,
-                                    "Revoking permission " + permName + " from package "
-                                            + packageName + " due to definition change");
-                        }
-                        Slog.e(TAG, "Revoking permission " + permName + " from package "
-                                + packageName + " due to definition change");
-                        try {
-                            revokeRuntimePermissionInternal(packageName, permName,
-                                    false, callingUid, userId, null, mDefaultPermissionCallback);
-                        } catch (Exception e) {
-                            Slog.e(TAG, "Could not revoke " + permName + " from "
-                                    + packageName, e);
-                        }
-                    }
-                }
-            });
-        }
-    }
-
-    private List<String> addAllPermissionsInternal(@NonNull AndroidPackage pkg) {
-        final int N = ArrayUtils.size(pkg.getPermissions());
-        ArrayList<String> definitionChangedPermissions = new ArrayList<>();
-        for (int i=0; i<N; i++) {
-            ParsedPermission p = pkg.getPermissions().get(i);
-
-            // Assume by default that we did not install this permission into the system.
-            ComponentMutateUtils.setExactFlags(p, p.getFlags() & ~PermissionInfo.FLAG_INSTALLED);
-
-            final PermissionInfo permissionInfo;
-            final Permission oldPermission;
-            synchronized (mLock) {
-                // Now that permission groups have a special meaning, we ignore permission
-                // groups for legacy apps to prevent unexpected behavior. In particular,
-                // permissions for one app being granted to someone just because they happen
-                // to be in a group defined by another app (before this had no implications).
-                if (pkg.getTargetSdkVersion() > Build.VERSION_CODES.LOLLIPOP_MR1) {
-                    ComponentMutateUtils.setParsedPermissionGroup(p,
-                            mRegistry.getPermissionGroup(p.getGroup()));
-                    // Warn for a permission in an unknown group.
-                    if (DEBUG_PERMISSIONS
-                            && p.getGroup() != null && p.getParsedPermissionGroup() == null) {
-                        Slog.i(TAG, "Permission " + p.getName() + " from package "
-                                + p.getPackageName() + " in an unknown group " + p.getGroup());
-                    }
-                }
-
-                permissionInfo = PackageInfoUtils.generatePermissionInfo(p,
-                        PackageManager.GET_META_DATA);
-                oldPermission = p.isTree() ? mRegistry.getPermissionTree(p.getName())
-                        : mRegistry.getPermission(p.getName());
-            }
-            // TODO(zhanghai): Maybe we should store whether a permission is owned by system inside
-            //  itself.
-            final boolean isOverridingSystemPermission = Permission.isOverridingSystemPermission(
-                    oldPermission, permissionInfo, mPackageManagerInt);
-            synchronized (mLock) {
-                final Permission permission = Permission.createOrUpdate(oldPermission,
-                        permissionInfo, pkg, mRegistry.getPermissionTrees(),
-                        isOverridingSystemPermission);
-                if (p.isTree()) {
-                    mRegistry.addPermissionTree(permission);
-                } else {
-                    mRegistry.addPermission(permission);
-                }
-                if (permission.isInstalled()) {
-                    ComponentMutateUtils.setExactFlags(p,
-                            p.getFlags() | PermissionInfo.FLAG_INSTALLED);
-                }
-                if (permission.isDefinitionChanged()) {
-                    definitionChangedPermissions.add(p.getName());
-                    permission.setDefinitionChanged(false);
-                }
-            }
-        }
-        return definitionChangedPermissions;
-    }
-
-    private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) {
-        synchronized (mLock) {
-            final int N = ArrayUtils.size(pkg.getPermissionGroups());
-            StringBuilder r = null;
-            for (int i = 0; i < N; i++) {
-                final ParsedPermissionGroup pg = pkg.getPermissionGroups().get(i);
-                final ParsedPermissionGroup cur = mRegistry.getPermissionGroup(pg.getName());
-                final String curPackageName = (cur == null) ? null : cur.getPackageName();
-                final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName);
-                if (cur == null || isPackageUpdate) {
-                    mRegistry.addPermissionGroup(pg);
-                    if (DEBUG_PACKAGE_SCANNING) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        if (isPackageUpdate) {
-                            r.append("UPD:");
-                        }
-                        r.append(pg.getName());
-                    }
-                } else {
-                    Slog.w(TAG, "Permission group " + pg.getName() + " from package "
-                            + pg.getPackageName() + " ignored: original from "
-                            + cur.getPackageName());
-                    if (DEBUG_PACKAGE_SCANNING) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        r.append("DUP:");
-                        r.append(pg.getName());
-                    }
-                }
-            }
-            if (r != null && DEBUG_PACKAGE_SCANNING) {
-                Log.d(TAG, "  Permission Groups: " + r);
-            }
-        }
-    }
-
-    private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) {
-        synchronized (mLock) {
-            int N = ArrayUtils.size(pkg.getPermissions());
-            StringBuilder r = null;
-            for (int i=0; i<N; i++) {
-                ParsedPermission p = pkg.getPermissions().get(i);
-                Permission bp = mRegistry.getPermission(p.getName());
-                if (bp == null) {
-                    bp = mRegistry.getPermissionTree(p.getName());
-                }
-                if (bp != null && bp.isPermission(p)) {
-                    bp.setPermissionInfo(null);
-                    if (DEBUG_REMOVE) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        r.append(p.getName());
-                    }
-                }
-                if (ParsedPermissionUtils.isAppOp(p)) {
-                    // TODO(zhanghai): Should we just remove the entry for this permission directly?
-                    mRegistry.removeAppOpPermissionPackage(p.getName(), pkg.getPackageName());
-                }
-            }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-            }
-
-            N = pkg.getRequestedPermissions().size();
-            r = null;
-            for (int i=0; i<N; i++) {
-                final String permissionName = pkg.getRequestedPermissions().get(i);
-                final Permission permission = mRegistry.getPermission(permissionName);
-                if (permission != null && permission.isAppOp()) {
-                    mRegistry.removeAppOpPermissionPackage(permissionName,
-                            pkg.getPackageName());
-                }
-            }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-            }
-        }
-    }
-
-    private void onUserRemoved(@UserIdInt int userId) {
-        synchronized (mLock) {
-            mState.removeUserState(userId);
-        }
-    }
-
-    @NonNull
-    private Set<String> getGrantedPermissionsInternal(@NonNull String packageName,
-            @UserIdInt int userId) {
-        final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName);
-        if (ps == null) {
-            return Collections.emptySet();
-        }
-
-        synchronized (mLock) {
-            final UidPermissionState uidState = getUidStateLocked(ps, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
-                return Collections.emptySet();
-            }
-            if (!ps.getUserStateOrDefault(userId).isInstantApp()) {
-                return uidState.getGrantedPermissions();
-            } else {
-                // Install permission state is shared among all users, but instant app state is
-                // per-user, so we can only filter it here unless we make install permission state
-                // per-user as well.
-                final Set<String> instantPermissions =
-                        new ArraySet<>(uidState.getGrantedPermissions());
-                instantPermissions.removeIf(permissionName -> {
-                    Permission permission = mRegistry.getPermission(permissionName);
-                    if (permission == null) {
-                        return true;
-                    }
-                    if (!permission.isInstant()) {
-                        EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId,
-                                ps.getAppId()), permissionName);
-                        return true;
-                    }
-                    return false;
-                });
-                return instantPermissions;
-            }
-        }
-    }
-
-    @NonNull
-    private int[] getPermissionGidsInternal(@NonNull String permissionName, @UserIdInt int userId) {
-        synchronized (mLock) {
-            Permission permission = mRegistry.getPermission(permissionName);
-            if (permission == null) {
-                return EmptyArray.INT;
-            }
-            return permission.computeGids(userId);
-        }
-    }
-
-    /**
-     * Restore the permission state for a package.
-     *
-     * <ul>
-     *     <li>During boot the state gets restored from the disk</li>
-     *     <li>During app update the state gets restored from the last version of the app</li>
-     * </ul>
-     *
-     * @param pkg the package the permissions belong to
-     * @param replace if the package is getting replaced (this might change the requested
-     *                permissions of this package)
-     * @param packageOfInterest If this is the name of {@code pkg} add extra logging
-     * @param callback Result call back
-     * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for
-     *                     this particular user
-     */
-    private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace,
-            @Nullable String packageOfInterest, @Nullable PermissionCallback callback,
-            @UserIdInt int filterUserId) {
-        // IMPORTANT: There are two types of permissions: install and runtime.
-        // Install time permissions are granted when the app is installed to
-        // all device users and users added in the future. Runtime permissions
-        // are granted at runtime explicitly to specific users. Normal and signature
-        // protected permissions are install time permissions. Dangerous permissions
-        // are install permissions if the app's target SDK is Lollipop MR1 or older,
-        // otherwise they are runtime permissions. This function does not manage
-        // runtime permissions except for the case an app targeting Lollipop MR1
-        // being upgraded to target a newer SDK, in which case dangerous permissions
-        // are transformed from install time to runtime ones.
-
-        final PackageStateInternal ps =
-                mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
-        if (ps == null) {
-            return;
-        }
-
-        final int[] userIds = filterUserId == UserHandle.USER_ALL ? getAllUserIds()
-                : new int[] { filterUserId };
-
-        boolean runtimePermissionsRevoked = false;
-        int[] updatedUserIds = EMPTY_INT_ARRAY;
-
-        ArraySet<String> isPrivilegedPermissionAllowlisted = null;
-        ArraySet<String> shouldGrantSignaturePermission = null;
-        ArraySet<String> shouldGrantInternalPermission = null;
-        ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
-        final List<String> requestedPermissions = pkg.getRequestedPermissions();
-        final int requestedPermissionsSize = requestedPermissions.size();
-        for (int i = 0; i < requestedPermissionsSize; i++) {
-            final String permissionName = pkg.getRequestedPermissions().get(i);
-
-            final Permission permission;
-            synchronized (mLock) {
-                permission = mRegistry.getPermission(permissionName);
-            }
-            if (permission == null) {
-                continue;
-            }
-            if (permission.isPrivileged()
-                    && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
-                if (isPrivilegedPermissionAllowlisted == null) {
-                    isPrivilegedPermissionAllowlisted = new ArraySet<>();
-                }
-                isPrivilegedPermissionAllowlisted.add(permissionName);
-            }
-            if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
-                    || shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
-                            shouldGrantPrivilegedPermissionIfWasGranted))) {
-                if (shouldGrantSignaturePermission == null) {
-                    shouldGrantSignaturePermission = new ArraySet<>();
-                }
-                shouldGrantSignaturePermission.add(permissionName);
-            }
-            if (permission.isInternal()
-                    && shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
-                            shouldGrantPrivilegedPermissionIfWasGranted)) {
-                if (shouldGrantInternalPermission == null) {
-                    shouldGrantInternalPermission = new ArraySet<>();
-                }
-                shouldGrantInternalPermission.add(permissionName);
-            }
-        }
-
-        final SparseBooleanArray isPermissionPolicyInitialized = new SparseBooleanArray();
-        if (mPermissionPolicyInternal != null) {
-            for (final int userId : userIds) {
-                if (mPermissionPolicyInternal.isInitialized(userId)) {
-                    isPermissionPolicyInitialized.put(userId, true);
-                }
-            }
-        }
-
-        synchronized (mLock) {
-            for (final int userId : userIds) {
-                final UserPermissionState userState = mState.getOrCreateUserState(userId);
-                final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId());
-
-                if (uidState.isMissing()) {
-                    Collection<String> uidRequestedPermissions;
-                    int targetSdkVersion;
-                    if (ps.getSharedUser() == null) {
-                        uidRequestedPermissions = pkg.getRequestedPermissions();
-                        targetSdkVersion = pkg.getTargetSdkVersion();
-                    } else {
-                        uidRequestedPermissions = new ArraySet<>();
-                        targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-                        List<AndroidPackage> packages = ps.getSharedUser().getPackages();
-                        int packagesSize = packages.size();
-                        for (int i = 0; i < packagesSize; i++) {
-                            AndroidPackage sharedUserPackage = packages.get(i);
-                            uidRequestedPermissions.addAll(
-                                    sharedUserPackage.getRequestedPermissions());
-                            targetSdkVersion = Math.min(targetSdkVersion,
-                                    sharedUserPackage.getTargetSdkVersion());
-                        }
-                    }
-
-                    for (String permissionName : uidRequestedPermissions) {
-                        Permission permission = mRegistry.getPermission(permissionName);
-                        if (permission == null) {
-                            continue;
-                        }
-                        if (Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)
-                                && permission.isRuntime() && !permission.isRemoved()) {
-                            if (permission.isHardOrSoftRestricted()
-                                    || permission.isImmutablyRestricted()) {
-                                uidState.updatePermissionFlags(permission,
-                                        FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
-                                        FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
-                            }
-                            if (targetSdkVersion < Build.VERSION_CODES.M) {
-                                uidState.updatePermissionFlags(permission,
-                                        PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-                                                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                                        PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-                                                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
-                                uidState.grantPermission(permission);
-                            }
-                        }
-                    }
-
-                    uidState.setMissing(false);
-                    updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                }
-
-                UidPermissionState origState = uidState;
-
-                boolean changedInstallPermission = false;
-
-                if (replace) {
-                    userState.setInstallPermissionsFixed(ps.getPackageName(), false);
-                    if (ps.getSharedUser() == null) {
-                        origState = new UidPermissionState(uidState);
-                        uidState.reset();
-                    } else {
-                        // We need to know only about runtime permission changes since the
-                        // calling code always writes the install permissions state but
-                        // the runtime ones are written only if changed. The only cases of
-                        // changed runtime permissions here are promotion of an install to
-                        // runtime and revocation of a runtime from a shared user.
-                        if (revokeUnusedSharedUserPermissionsLocked(
-                                ps.getSharedUser().getPackages(), uidState)) {
-                            updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                            runtimePermissionsRevoked = true;
-                        }
-                    }
-                }
-
-                ArraySet<String> newImplicitPermissions = new ArraySet<>();
-                final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
-
-                for (int i = 0; i < requestedPermissionsSize; i++) {
-                    final String permName = requestedPermissions.get(i);
-
-                    final Permission bp = mRegistry.getPermission(permName);
-                    final boolean appSupportsRuntimePermissions =
-                            pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
-                    String legacyActivityRecognitionPermission = null;
-
-                    if (DEBUG_INSTALL && bp != null) {
-                        Log.i(TAG, "Package " + friendlyName
-                                + " checking " + permName + ": " + bp);
-                    }
-
-                    // TODO(zhanghai): I don't think we need to check source package setting if
-                    //  permission is present, because otherwise the permission should have been
-                    //  removed.
-                    if (bp == null /*|| getSourcePackageSetting(bp) == null*/) {
-                        if (packageOfInterest == null || packageOfInterest.equals(
-                                pkg.getPackageName())) {
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, "Unknown permission " + permName
-                                        + " in package " + friendlyName);
-                            }
-                        }
-                        continue;
-                    }
-
-                    // Cache newImplicitPermissions before modifing permissionsState as for the
-                    // shared uids the original and new state are the same object
-                    if (!origState.hasPermissionState(permName)
-                            && (pkg.getImplicitPermissions().contains(permName)
-                            || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
-                        if (pkg.getImplicitPermissions().contains(permName)) {
-                            // If permName is an implicit permission, try to auto-grant
-                            newImplicitPermissions.add(permName);
-
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, permName + " is newly added for " + friendlyName);
-                            }
-                        } else {
-                            // Special case for Activity Recognition permission. Even if AR
-                            // permission is not an implicit permission we want to add it to the
-                            // list (try to auto-grant it) if the app was installed on a device
-                            // before AR permission was split, regardless of if the app now requests
-                            // the new AR permission or has updated its target SDK and AR is no
-                            // longer implicit to it. This is a compatibility workaround for apps
-                            // when AR permission was split in Q.
-                            // TODO(zhanghai): This calls into SystemConfig, which generally
-                            //  shouldn't  cause deadlock, but maybe we should keep a cache of the
-                            //  split permission  list and just eliminate the possibility.
-                            final List<PermissionManager.SplitPermissionInfo> permissionList =
-                                    getSplitPermissionInfos();
-                            int numSplitPerms = permissionList.size();
-                            for (int splitPermNum = 0; splitPermNum < numSplitPerms;
-                                    splitPermNum++) {
-                                PermissionManager.SplitPermissionInfo sp = permissionList.get(
-                                        splitPermNum);
-                                String splitPermName = sp.getSplitPermission();
-                                if (sp.getNewPermissions().contains(permName)
-                                        && origState.isPermissionGranted(splitPermName)) {
-                                    legacyActivityRecognitionPermission = splitPermName;
-                                    newImplicitPermissions.add(permName);
-
-                                    if (DEBUG_PERMISSIONS) {
-                                        Slog.i(TAG, permName + " is newly added for "
-                                                + friendlyName);
-                                    }
-                                    break;
-                                }
-                            }
-                        }
-                    }
-
-                    // TODO(b/140256621): The package instant app method has been removed
-                    //  as part of work in b/135203078, so this has been commented out in the
-                    //  meantime
-                    // Limit ephemeral apps to ephemeral allowed permissions.
-        //            if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
-        //                if (DEBUG_PERMISSIONS) {
-        //                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
-        //                            + " for package " + pkg.getPackageName());
-        //                }
-        //                continue;
-        //            }
-
-                    if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
-                        if (DEBUG_PERMISSIONS) {
-                            Log.i(TAG, "Denying runtime-only permission " + bp.getName()
-                                    + " for package " + friendlyName);
-                        }
-                        continue;
-                    }
-
-                    final String perm = bp.getName();
-
-                    // Keep track of app op permissions.
-                    if (bp.isAppOp()) {
-                        mRegistry.addAppOpPermissionPackage(perm, pkg.getPackageName());
-                    }
-
-                    boolean shouldGrantNormalPermission = true;
-                    if (bp.isNormal() && !origState.isPermissionGranted(perm)) {
-                        // If this is an existing, non-system package, then
-                        // we can't add any new permissions to it. Runtime
-                        // permissions can be added any time - they are dynamic.
-                        if (!ps.isSystem() && userState.areInstallPermissionsFixed(
-                                ps.getPackageName())) {
-                            // Except...  if this is a permission that was added
-                            // to the platform (note: need to only do this when
-                            // updating the platform).
-                            if (!isCompatPlatformPermissionForPackage(perm, pkg)) {
-                                shouldGrantNormalPermission = false;
-                            }
-                        }
-                    }
-
-                    if (DEBUG_PERMISSIONS) {
-                        Slog.i(TAG, "Considering granting permission " + perm + " to package "
-                                + pkg.getPackageName());
-                    }
-
-                    if ((bp.isNormal() && shouldGrantNormalPermission)
-                            || (bp.isSignature()
-                                    && (!bp.isPrivileged() || CollectionUtils.contains(
-                                            isPrivilegedPermissionAllowlisted, permName))
-                                    && (CollectionUtils.contains(shouldGrantSignaturePermission,
-                                            permName)
-                                            || (((bp.isPrivileged() && CollectionUtils.contains(
-                                                    shouldGrantPrivilegedPermissionIfWasGranted,
-                                                    permName)) || bp.isDevelopment() || bp.isRole())
-                                                    && origState.isPermissionGranted(permName))))
-                            || (bp.isInternal()
-                                    && (!bp.isPrivileged() || CollectionUtils.contains(
-                                            isPrivilegedPermissionAllowlisted, permName))
-                                    && (CollectionUtils.contains(shouldGrantInternalPermission,
-                                            permName)
-                                            || (((bp.isPrivileged() && CollectionUtils.contains(
-                                                    shouldGrantPrivilegedPermissionIfWasGranted,
-                                                    permName)) || bp.isDevelopment() || bp.isRole())
-                                                    && origState.isPermissionGranted(permName))))) {
-                        // Grant an install permission.
-                        if (uidState.grantPermission(bp)) {
-                            changedInstallPermission = true;
-                        }
-                    } else if (bp.isRuntime()) {
-                        boolean hardRestricted = bp.isHardRestricted();
-                        boolean softRestricted = bp.isSoftRestricted();
-
-                        // If permission policy is not ready we don't deal with restricted
-                        // permissions as the policy may allowlist some permissions. Once
-                        // the policy is initialized we would re-evaluate permissions.
-                        final boolean permissionPolicyInitialized =
-                                isPermissionPolicyInitialized.get(userId);
-
-                        PermissionState origPermState = origState.getPermissionState(perm);
-                        int flags = origPermState != null ? origPermState.getFlags() : 0;
-
-                        boolean wasChanged = false;
-
-                        boolean restrictionExempt =
-                                (origState.getPermissionFlags(bp.getName())
-                                        & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
-                        boolean restrictionApplied = (origState.getPermissionFlags(
-                                bp.getName()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
-
-                        if (appSupportsRuntimePermissions) {
-                            // If hard restricted we don't allow holding it
-                            if (permissionPolicyInitialized && hardRestricted) {
-                                if (!restrictionExempt) {
-                                    if (origPermState != null && origPermState.isGranted()
-                                            && uidState.revokePermission(bp)) {
-                                        wasChanged = true;
-                                    }
-                                    if (!restrictionApplied) {
-                                        flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
-                                        wasChanged = true;
-                                    }
-                                }
-                            // If soft restricted we allow holding in a restricted form
-                            } else if (permissionPolicyInitialized && softRestricted) {
-                                // Regardless if granted set the restriction flag as it
-                                // may affect app treatment based on this permission.
-                                if (!restrictionExempt && !restrictionApplied) {
-                                    flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
-                                    wasChanged = true;
-                                }
-                            }
-
-                            // Remove review flag as it is not necessary anymore
-                            if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                                flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
-                                wasChanged = true;
-                            }
-
-                            if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0
-                                    && !isPermissionSplitFromNonRuntime(permName,
-                                    pkg.getTargetSdkVersion())) {
-                                flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
-                                wasChanged = true;
-                            // Hard restricted permissions cannot be held.
-                            } else if (!permissionPolicyInitialized
-                                    || (!hardRestricted || restrictionExempt)) {
-                                if ((origPermState != null && origPermState.isGranted())
-                                        || legacyActivityRecognitionPermission != null) {
-                                    if (!uidState.grantPermission(bp)) {
-                                        wasChanged = true;
-                                    }
-                                }
-                            }
-                        } else {
-                            if (origPermState == null) {
-                                // New permission
-                                if (PLATFORM_PACKAGE_NAME.equals(
-                                        bp.getPackageName())) {
-                                    if (!bp.isRemoved()) {
-                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED
-                                                | FLAG_PERMISSION_REVOKED_COMPAT;
-                                        wasChanged = true;
-                                    }
-                                }
-                            }
-
-                            if (!uidState.isPermissionGranted(bp.getName())
-                                    && uidState.grantPermission(bp)) {
-                                wasChanged = true;
-                            }
-
-                            // If legacy app always grant the permission but if restricted
-                            // and not exempt take a note a restriction should be applied.
-                            if (permissionPolicyInitialized
-                                    && (hardRestricted || softRestricted)
-                                            && !restrictionExempt && !restrictionApplied) {
-                                flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
-                                wasChanged = true;
-                            }
-                        }
-
-                        // If unrestricted or restriction exempt, don't apply restriction.
-                        if (permissionPolicyInitialized) {
-                            if (!(hardRestricted || softRestricted) || restrictionExempt) {
-                                if (restrictionApplied) {
-                                    flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION;
-                                    // Dropping restriction on a legacy app implies a review
-                                    if (!appSupportsRuntimePermissions) {
-                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
-                                    }
-                                    wasChanged = true;
-                                }
-                            }
-                        }
-
-                        if (wasChanged) {
-                            updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                        }
-
-                        uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
-                                flags);
-                    } else {
-                        if (DEBUG_PERMISSIONS) {
-                            boolean wasGranted = uidState.isPermissionGranted(bp.getName());
-                            if (wasGranted || bp.isAppOp()) {
-                                Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
-                                        + " permission " + perm
-                                        + " from package " + friendlyName
-                                        + " (protectionLevel=" + bp.getProtectionLevel()
-                                        + " flags=0x"
-                                        + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
-                                                ps))
-                                        + ")");
-                            }
-                        }
-                        if (uidState.removePermissionState(bp.getName())) {
-                            changedInstallPermission = true;
-                        }
-                    }
-                }
-
-                if ((changedInstallPermission || replace)
-                        && !userState.areInstallPermissionsFixed(ps.getPackageName())
-                        && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) {
-                    // This is the first that we have heard about this package, so the
-                    // permissions we have now selected are fixed until explicitly
-                    // changed.
-                    userState.setInstallPermissionsFixed(ps.getPackageName(), true);
-                }
-
-                updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg,
-                        userId, updatedUserIds);
-                updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState,
-                        uidState, pkg, newImplicitPermissions, userId, updatedUserIds);
-            }
-        }
-
-        updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
-                updatedUserIds);
-
-        // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important
-        //  for shared users.
-        // Persist the runtime permissions state for users with changes. If permissions
-        // were revoked because no app in the shared user declares them we have to
-        // write synchronously to avoid losing runtime permissions state.
-        if (callback != null) {
-            callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked);
-        }
-
-        for (int userId : updatedUserIds) {
-            notifyRuntimePermissionStateChanged(pkg.getPackageName(), userId);
-        }
-    }
-
-    /**
-     * Returns all relevant user ids.  This list include the current set of created user ids as well
-     * as pre-created user ids.
-     * @return user ids for created users and pre-created users
-     */
-    private int[] getAllUserIds() {
-        return UserManagerService.getInstance().getUserIdsIncludingPreCreated();
-    }
-
-    /**
-     * Revoke permissions that are not implicit anymore and that have
-     * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set.
-     *
-     * @param ps The state of the permissions of the package
-     * @param pkg The package that is currently looked at
-     * @param userIds All user IDs in the system, must be passed in because this method is locked
-     * @param updatedUserIds a list of user ids that needs to be amended if the permission state
-     *                       for a user is changed.
-     *
-     * @return The updated value of the {@code updatedUserIds} parameter
-     */
-    @GuardedBy("mLock")
-    private @NonNull int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps,
-            @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) {
-        String pkgName = pkg.getPackageName();
-        boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
-                >= Build.VERSION_CODES.M;
-
-        for (String permission : ps.getGrantedPermissions()) {
-            if (pkg.getRequestedPermissions().contains(permission)
-                    && !pkg.getImplicitPermissions().contains(permission)) {
-                Permission bp = mRegistry.getPermission(permission);
-                if (bp != null && bp.isRuntime()) {
-                    int flags = ps.getPermissionFlags(permission);
-                    if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
-                        int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
-
-                        // We're willing to preserve an implicit "Nearby devices"
-                        // permission grant if this app was already able to interact
-                        // with nearby devices via background location access
-                        boolean preserveGrant = false;
-                        if (ArrayUtils.contains(NEARBY_DEVICES_PERMISSIONS, permission)
-                                && ps.isPermissionGranted(
-                                        android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
-                                && (ps.getPermissionFlags(
-                                        android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
-                                        & (FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
-                                                | FLAG_PERMISSION_REVOKED_COMPAT)) == 0) {
-                            preserveGrant = true;
-                        }
-
-                        if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
-                                && supportsRuntimePermissions
-                                && !preserveGrant) {
-                            if (ps.revokePermission(bp)) {
-                                if (DEBUG_PERMISSIONS) {
-                                    Slog.i(TAG, "Revoking runtime permission "
-                                            + permission + " for " + pkgName
-                                            + " as it is now requested");
-                                }
-                            }
-
-                            flagsToRemove |= USER_PERMISSION_FLAGS;
-                        }
-
-                        ps.updatePermissionFlags(bp, flagsToRemove, 0);
-                        updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                    }
-                }
-            }
-        }
-
-        return updatedUserIds;
-    }
-
-    /**
-     * {@code newPerm} is newly added; Inherit the state from {@code sourcePerms}.
-     *
-     * <p>A single new permission can be split off from several source permissions. In this case
-     * the most leniant state is inherited.
-     *
-     * <p>Warning: This does not handle foreground / background permissions
-     *
-     * @param sourcePerms The permissions to inherit from
-     * @param newPerm The permission to inherit to
-     * @param ps The permission state of the package
-     * @param pkg The package requesting the permissions
-     */
-    @GuardedBy("mLock")
-    private void inheritPermissionStateToNewImplicitPermissionLocked(
-            @NonNull ArraySet<String> sourcePerms, @NonNull String newPerm,
-            @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) {
-        String pkgName = pkg.getPackageName();
-        boolean isGranted = false;
-        int flags = 0;
-
-        int numSourcePerm = sourcePerms.size();
-        for (int i = 0; i < numSourcePerm; i++) {
-            String sourcePerm = sourcePerms.valueAt(i);
-            if (ps.isPermissionGranted(sourcePerm)) {
-                if (!isGranted) {
-                    flags = 0;
-                }
-
-                isGranted = true;
-                flags |= ps.getPermissionFlags(sourcePerm);
-            } else {
-                if (!isGranted) {
-                    flags |= ps.getPermissionFlags(sourcePerm);
-                }
-            }
-        }
-
-        if (isGranted) {
-            if (DEBUG_PERMISSIONS) {
-                Slog.i(TAG, newPerm + " inherits runtime perm grant from " + sourcePerms
-                        + " for " + pkgName);
-            }
-
-            ps.grantPermission(mRegistry.getPermission(newPerm));
-        }
-
-        // Add permission flags
-        ps.updatePermissionFlags(mRegistry.getPermission(newPerm), flags, flags);
-    }
-
-    /**
-     * When the app has requested legacy storage we might need to update
-     * {@link android.app.AppOpsManager#OP_LEGACY_STORAGE}. Hence force an update in
-     * {@link com.android.server.policy.PermissionPolicyService#synchronizePackagePermissionsAndAppOpsForUser(Context, String, int)}
-     *
-     * @param pkg The package for which the permissions are updated
-     * @param replace If the app is being replaced
-     * @param userIds All user IDs in the system, must be passed in because this method is locked
-     * @param updatedUserIds The ids of the users that already changed.
-     *
-     * @return The ids of the users that are changed
-     */
-    private @NonNull int[] checkIfLegacyStorageOpsNeedToBeUpdated(@NonNull AndroidPackage pkg,
-            boolean replace, @NonNull int[] userIds, @NonNull int[] updatedUserIds) {
-        if (replace && pkg.isRequestLegacyExternalStorage() && (
-                pkg.getRequestedPermissions().contains(READ_EXTERNAL_STORAGE)
-                        || pkg.getRequestedPermissions().contains(WRITE_EXTERNAL_STORAGE))) {
-            return userIds.clone();
-        }
-
-        return updatedUserIds;
-    }
-
-    /**
-     * Set the state of a implicit permission that is seen for the first time.
-     *
-     * @param origPs The permission state of the package before the split
-     * @param ps The new permission state
-     * @param pkg The package the permission belongs to
-     * @param userId The user ID
-     * @param updatedUserIds List of users for which the permission state has already been changed
-     *
-     * @return  List of users for which the permission state has been changed
-     */
-    @GuardedBy("mLock")
-    private @NonNull int[] setInitialGrantForNewImplicitPermissionsLocked(
-            @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps,
-            @NonNull AndroidPackage pkg, @NonNull ArraySet<String> newImplicitPermissions,
-            @UserIdInt int userId, @NonNull int[] updatedUserIds) {
-        String pkgName = pkg.getPackageName();
-        ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>();
-
-        final List<PermissionManager.SplitPermissionInfo> permissionList =
-                getSplitPermissionInfos();
-        int numSplitPerms = permissionList.size();
-        for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
-            PermissionManager.SplitPermissionInfo spi = permissionList.get(splitPermNum);
-
-            List<String> newPerms = spi.getNewPermissions();
-            int numNewPerms = newPerms.size();
-            for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
-                String newPerm = newPerms.get(newPermNum);
-
-                ArraySet<String> splitPerms = newToSplitPerms.get(newPerm);
-                if (splitPerms == null) {
-                    splitPerms = new ArraySet<>();
-                    newToSplitPerms.put(newPerm, splitPerms);
-                }
-
-                splitPerms.add(spi.getSplitPermission());
-            }
-        }
-
-        int numNewImplicitPerms = newImplicitPermissions.size();
-        for (int newImplicitPermNum = 0; newImplicitPermNum < numNewImplicitPerms;
-                newImplicitPermNum++) {
-            String newPerm = newImplicitPermissions.valueAt(newImplicitPermNum);
-            ArraySet<String> sourcePerms = newToSplitPerms.get(newPerm);
-
-            if (sourcePerms != null) {
-                Permission bp = mRegistry.getPermission(newPerm);
-                if (bp == null) {
-                    throw new IllegalStateException("Unknown new permission in split permission: "
-                            + newPerm);
-                }
-                if (bp.isRuntime()) {
-
-                    if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
-                        ps.updatePermissionFlags(bp,
-                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
-                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
-                    }
-                    updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-
-                    if (!origPs.hasPermissionState(sourcePerms)) {
-                        boolean inheritsFromInstallPerm = false;
-                        for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
-                                sourcePermNum++) {
-                            final String sourcePerm = sourcePerms.valueAt(sourcePermNum);
-                            Permission sourceBp = mRegistry.getPermission(sourcePerm);
-                            if (sourceBp == null) {
-                                throw new IllegalStateException("Unknown source permission in split"
-                                        + " permission: " + sourcePerm);
-                            }
-                            if (!sourceBp.isRuntime()) {
-                                inheritsFromInstallPerm = true;
-                                break;
-                            }
-                        }
-
-                        if (!inheritsFromInstallPerm) {
-                            // Both permissions are new so nothing to inherit.
-                            if (DEBUG_PERMISSIONS) {
-                                Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
-                                        + " for " + pkgName + " as split permission is also new");
-                            }
-                            continue;
-                        }
-                    }
-
-                    // Inherit from new install or existing runtime permissions
-                    inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps,
-                            pkg);
-                }
-            } else if (IMPLICIT_GRANTED_PERMISSIONS.contains(newPerm)
-                    && !origPs.hasPermissionState(newPerm)) {
-                Permission bp = mRegistry.getPermission(newPerm);
-                if (bp == null) {
-                    throw new IllegalStateException("Unknown new permission " + newPerm);
-                }
-                if ((ps.getPermissionState(newPerm).getFlags()
-                        & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                    // No need to grant if review is required
-                    continue;
-                }
-                updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
-                ps.updatePermissionFlags(bp,
-                        FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
-                        FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
-                ps.grantPermission(bp);
-            }
-        }
-
-        return updatedUserIds;
-    }
-
-    @NonNull
-    @Override
-    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
-        return PermissionManager.splitPermissionInfoListToParcelableList(getSplitPermissionInfos());
-    }
-
-    @NonNull
-    private List<PermissionManager.SplitPermissionInfo> getSplitPermissionInfos() {
-        return SystemConfig.getInstance().getSplitPermissions();
-    }
-
     @NonNull
     private OneTimePermissionUserManager getOneTimePermissionUserManager(@UserIdInt int userId) {
         OneTimePermissionUserManager oneTimePermissionUserManager;
@@ -3452,1675 +449,125 @@
         return result;
     }
 
-    private static boolean isCompatPlatformPermissionForPackage(String perm, AndroidPackage pkg) {
-        boolean allowed = false;
-        for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) {
-            final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i];
-            if (info.getName().equals(perm)
-                    && pkg.getTargetSdkVersion() < info.getSdkVersion()) {
-                allowed = true;
-                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
-                        + pkg.getPackageName());
-                break;
-            }
-        }
-        return allowed;
+    /* Start of delegate methods to PermissionManagerServiceInterface */
+
+    @Override
+    public ParceledListSlice<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        return new ParceledListSlice<>(mPermissionManagerServiceImpl.getAllPermissionGroups(flags));
     }
 
-    private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
-            @NonNull PackageStateInternal packageSetting, @NonNull Permission permission) {
-        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
-            return true;
-        }
-        final String packageName = pkg.getPackageName();
-        if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
-            return true;
-        }
-        if (!pkg.isPrivileged()) {
-            return true;
-        }
-        if (!mPrivilegedPermissionAllowlistSourcePackageNames
-                .contains(permission.getPackageName())) {
-            return true;
-        }
-        final String permissionName = permission.getName();
-        final ApexManager apexManager = ApexManager.getInstance();
-        final String containingApexPackageName =
-                apexManager.getActiveApexPackageNameContainingPackage(packageName);
-        if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
-                containingApexPackageName)) {
-            return true;
-        }
-        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName,
-                containingApexPackageName)) {
-            return false;
-        }
-        // Updated system apps do not need to be allowlisted
-        if (packageSetting.getTransientState().isUpdatedSystemApp()) {
-            // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission
-            // can be granted, because an updated system app may be in a shared UID, and in case a
-            // new privileged permission is requested by the updated system app but not the factory
-            // app, although this app and permission combination isn't in the allowlist and can't
-            // get the permission this way, other apps in the shared UID may still get it. A proper
-            // fix for this would be to perform the reconciliation by UID, but for now let's keep
-            // the old workaround working, which is to keep granted privileged permissions still
-            // granted.
-            return true;
-        }
-        // Only enforce the allowlist on boot
-        if (!mSystemReady) {
-            final boolean isInUpdatedApex = containingApexPackageName != null
-                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
-                    MATCH_ACTIVE_PACKAGE));
-            // Apps that are in updated apexs' do not need to be allowlisted
-            if (!isInUpdatedApex) {
-                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
-                        + packageName + " (" + pkg.getPath()
-                        + ") not in privapp-permissions allowlist");
-                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
-                    synchronized (mLock) {
-                        if (mPrivappPermissionsViolations == null) {
-                            mPrivappPermissionsViolations = new ArraySet<>();
-                        }
-                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
-                                + permissionName);
-                    }
-                }
-            }
-        }
-        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) {
+        return mPermissionManagerServiceImpl.getPermissionGroupInfo(groupName, flags);
     }
 
-    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission, String containingApexPackageName) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
-        } else if (containingApexPackageName != null) {
-            final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
-                    pkg.getPackageName());
-            final Set<String> apexPermissions = systemConfig.getApexPrivAppPermissions(
-                    containingApexPackageName, pkg.getPackageName());
-            if (privAppPermissions != null) {
-                // TODO(andreionea): Remove check as soon as all apk-in-apex
-                // permission allowlists are migrated.
-                Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX,"
-                        + " but has permission allowlist on the system image. Please bundle the"
-                        + " allowlist in the " + containingApexPackageName + " APEX instead.");
-                if (apexPermissions != null) {
-                    permissions = new ArraySet<>(privAppPermissions);
-                    permissions.addAll(apexPermissions);
-                } else {
-                    permissions = privAppPermissions;
-                }
-            } else {
-                permissions = apexPermissions;
-            }
-        } else {
-            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
-        }
-        return CollectionUtils.contains(permissions, permission);
+    @Override
+    public PermissionInfo getPermissionInfo(String permissionName, String packageName, int flags) {
+        return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, packageName, flags);
     }
 
-    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
-            @NonNull String permission, String containingApexPackageName) {
-        final SystemConfig systemConfig = SystemConfig.getInstance();
-        final Set<String> permissions;
-        if (pkg.isVendor()) {
-            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isProduct()) {
-            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (pkg.isSystemExt()) {
-            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
-        } else if (containingApexPackageName != null) {
-            permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName,
-                    pkg.getPackageName());
-        } else {
-            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
-        }
-        return CollectionUtils.contains(permissions, permission);
-    }
-
-    private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
-            @NonNull Permission bp) {
-        // expect single system package
-        String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
-                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM));
-        final AndroidPackage systemPackage =
-                mPackageManagerInt.getPackage(systemPackageName);
-        // 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
-        final SigningDetails sourceSigningDetails =
-                getSourcePackageSigningDetails(bp);
-        return sourceSigningDetails.hasCommonSignerWithCapability(
-                        pkg.getSigningDetails(),
-                        SigningDetails.CertCapabilities.PERMISSION)
-                || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails())
-                || systemPackage.getSigningDetails().checkCapability(
-                        pkg.getSigningDetails(),
-                        SigningDetails.CertCapabilities.PERMISSION);
-    }
-
-    private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
-            @NonNull PackageStateInternal pkgSetting, @NonNull Permission bp,
-            @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) {
-        boolean allowed = false;
-        final boolean isPrivilegedPermission = bp.isPrivileged();
-        final boolean isOemPermission = bp.isOem();
-        if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
-            final String permissionName = bp.getName();
-            // For updated system applications, a privileged/oem permission
-            // is granted only if it had been defined by the original application.
-            if (pkgSetting.getTransientState().isUpdatedSystemApp()) {
-                final PackageSetting disabledPs = mPackageManagerInt
-                        .getDisabledSystemPackage(pkg.getPackageName());
-                final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg();
-                if (disabledPkg != null
-                        && ((isPrivilegedPermission && disabledPkg.isPrivileged())
-                        || (isOemPermission && canGrantOemPermission(disabledPkg,
-                                permissionName)))) {
-                    if (disabledPkg.getRequestedPermissions().contains(permissionName)) {
-                        allowed = true;
-                    } else {
-                        // If the original was granted this permission, we take
-                        // that grant decision as read and propagate it to the
-                        // update.
-                        shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName);
-                    }
-                }
-            } else {
-                allowed = (isPrivilegedPermission && pkg.isPrivileged())
-                        || (isOemPermission && canGrantOemPermission(pkg, permissionName));
-            }
-            // 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 (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
-                Slog.w(TAG, "Permission " + permissionName
-                        + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
-                        + " because it isn't a 'vendorPrivileged' permission.");
-                allowed = false;
-            }
-        }
-        if (!allowed && bp.isPre23() && pkg.getTargetSdkVersion() < 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.
-            allowed = true;
-        }
-        // TODO (moltmann): The installer now shares the platforms signature. Hence it does not
-        //                  need a separate flag anymore. Hence we need to check which
-        //                  permissions are needed by the permission controller
-        if (!allowed && bp.isInstaller()
-                && (ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
-                pkg.getPackageName()) || ArrayUtils.contains(
-                        mPackageManagerInt.getKnownPackageNames(
-                                PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
-                UserHandle.USER_SYSTEM), pkg.getPackageName()))) {
-            // If this permission is to be granted to the system installer and
-            // this app is an installer, then it gets the permission.
-            allowed = true;
-        }
-        if (!allowed && bp.isVerifier()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
-                pkg.getPackageName())) {
-            // If this permission is to be granted to the system verifier and
-            // this app is a verifier, then it gets the permission.
-            allowed = true;
-        }
-        if (!allowed && bp.isPreInstalled()
-                && pkg.isSystem()) {
-            // Any pre-installed system app is allowed to get this permission.
-            allowed = true;
-        }
-        if (!allowed && bp.isKnownSigner()) {
-            // 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.
-            allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
-        }
-        // Deferred to be checked under permission data lock inside restorePermissionState().
-        //if (!allowed && bp.isDevelopment()) {
-        //    // For development permissions, a development permission
-        //    // is granted only if it was already granted.
-        //    allowed = origPermissions.isPermissionGranted(permissionName);
-        //}
-        if (!allowed && bp.isSetup()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
-                pkg.getPackageName())) {
-            // If this permission is to be granted to the system setup wizard and
-            // this app is a setup wizard, then it gets the permission.
-            allowed = true;
-        }
-        if (!allowed && bp.isSystemTextClassifier()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
-                UserHandle.USER_SYSTEM), pkg.getPackageName())) {
-            // Special permissions for the system default text classifier.
-            allowed = true;
-        }
-        if (!allowed && bp.isConfigurator()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_CONFIGURATOR,
-                UserHandle.USER_SYSTEM), pkg.getPackageName())) {
-            // Special permissions for the device configurator.
-            allowed = true;
-        }
-        if (!allowed && bp.isIncidentReportApprover()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
-                UserHandle.USER_SYSTEM), pkg.getPackageName())) {
-            // 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.
-            allowed = true;
-        }
-        if (!allowed && bp.isAppPredictor()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
-                pkg.getPackageName())) {
-            // Special permissions for the system app predictor.
-            allowed = true;
-        }
-        if (!allowed && bp.isCompanion()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                    PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM),
-                pkg.getPackageName())) {
-            // Special permissions for the system companion device manager.
-            allowed = true;
-        }
-        if (!allowed && bp.isRetailDemo()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                        PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM),
-                pkg.getPackageName()) && isProfileOwner(pkg.getUid())) {
-            // Special permission granted only to the OEM specified retail demo app
-            allowed = true;
-        }
-        if (!allowed && bp.isRecents()
-                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
-                PackageManagerInternal.PACKAGE_RECENTS, UserHandle.USER_SYSTEM),
-                pkg.getPackageName())) {
-            // Special permission for the recents app.
-            allowed = true;
-        }
-        return allowed;
-    }
-
-    @NonNull
-    private SigningDetails getSourcePackageSigningDetails(
-            @NonNull Permission bp) {
-        final PackageStateInternal ps = getSourcePackageSetting(bp);
-        if (ps == null) {
-            return SigningDetails.UNKNOWN;
-        }
-        return ps.getSigningDetails();
-    }
-
-    @Nullable
-    private PackageStateInternal getSourcePackageSetting(@NonNull Permission bp) {
-        final String sourcePackageName = bp.getPackageName();
-        return mPackageManagerInt.getPackageStateInternal(sourcePackageName);
-    }
-
-    private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
-        if (!pkg.isOem()) {
-            return false;
-        }
-        // all oem permissions must explicitly be granted or denied
-        final Boolean granted =
-                SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission);
-        if (granted == null) {
-            throw new IllegalStateException("OEM permission" + permission + " requested by package "
-                    + pkg.getPackageName() + " must be explicitly declared granted or not");
-        }
-        return Boolean.TRUE == granted;
-    }
-
-    private static boolean isProfileOwner(int uid) {
-        DevicePolicyManagerInternal dpmInternal =
-                LocalServices.getService(DevicePolicyManagerInternal.class);
-        //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
-        if (dpmInternal != null) {
-            return dpmInternal.isActiveProfileOwner(uid) || dpmInternal.isActiveDeviceOwner(uid);
-        }
-        return false;
-    }
-
-    private boolean isPermissionsReviewRequiredInternal(@NonNull String packageName,
-            @UserIdInt int userId) {
-        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-        if (pkg == null) {
-            return false;
-        }
-
-        // Permission review applies only to apps not supporting the new permission model.
-        if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
-            return false;
-        }
-
-        // Legacy apps have the permission and get user consent on launch.
-        synchronized (mLock) {
-            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
-                        + userId);
-                return false;
-            }
-            return uidState.isPermissionsReviewRequired();
-        }
-    }
-
-    private void grantRequestedRuntimePermissionsInternal(@NonNull AndroidPackage pkg,
-            @Nullable List<String> permissions, int userId) {
-        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-
-        final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
-                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
-
-        final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
-                >= Build.VERSION_CODES.M;
-
-        final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.getPackageName(), userId);
-
-        final int myUid = Process.myUid();
-
-        for (String permission : pkg.getRequestedPermissions()) {
-            final boolean shouldGrantPermission;
-            synchronized (mLock) {
-                final Permission bp = mRegistry.getPermission(permission);
-                shouldGrantPermission = bp != null && (bp.isRuntime() || bp.isDevelopment())
-                        && (!instantApp || bp.isInstant())
-                        && (supportsRuntimePermissions || !bp.isRuntimeOnly())
-                        && (permissions == null || permissions.contains(permission));
-            }
-            if (shouldGrantPermission) {
-                final int flags = getPermissionFlagsInternal(pkg.getPackageName(), permission,
-                        myUid, userId);
-                if (supportsRuntimePermissions) {
-                    // Installer cannot change immutable permissions.
-                    if ((flags & immutableFlags) == 0) {
-                        grantRuntimePermissionInternal(pkg.getPackageName(), permission, false,
-                                myUid, userId, mDefaultPermissionCallback);
-                    }
-                } else {
-                    // In permission review mode we clear the review flag and the revoked compat
-                    // flag when we are asked to install the app with all permissions granted.
-                    if ((flags & compatFlags) != 0) {
-                        updatePermissionFlagsInternal(pkg.getPackageName(), permission, compatFlags,
-                                0, myUid, userId, false, mDefaultPermissionCallback);
-                    }
-                }
-            }
-        }
-    }
-
-    private void setAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
-            @Nullable List<String> permissions, @PermissionWhitelistFlags int allowlistFlags,
-            @UserIdInt int userId) {
-        ArraySet<String> oldGrantedRestrictedPermissions = null;
-        boolean updatePermissions = false;
-        final int permissionCount = pkg.getRequestedPermissions().size();
-        final int myUid = Process.myUid();
-
-        for (int j = 0; j < permissionCount; j++) {
-            final String permissionName = pkg.getRequestedPermissions().get(j);
-
-            final boolean isGranted;
-            synchronized (mLock) {
-                final Permission bp = mRegistry.getPermission(permissionName);
-                if (bp == null || !bp.isHardOrSoftRestricted()) {
-                    continue;
-                }
-
-                final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-                if (uidState == null) {
-                    Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
-                            + " and user " + userId);
-                    continue;
-                }
-                isGranted = uidState.isPermissionGranted(permissionName);
-            }
-
-            if (isGranted) {
-                if (oldGrantedRestrictedPermissions == null) {
-                    oldGrantedRestrictedPermissions = new ArraySet<>();
-                }
-                oldGrantedRestrictedPermissions.add(permissionName);
-            }
-
-            final int oldFlags = getPermissionFlagsInternal(pkg.getPackageName(), permissionName,
-                    myUid, userId);
-
-            int newFlags = oldFlags;
-            int mask = 0;
-            int allowlistFlagsCopy = allowlistFlags;
-            while (allowlistFlagsCopy != 0) {
-                final int flag = 1 << Integer.numberOfTrailingZeros(allowlistFlagsCopy);
-                allowlistFlagsCopy &= ~flag;
-                switch (flag) {
-                    case FLAG_PERMISSION_WHITELIST_SYSTEM: {
-                        mask |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-                        if (permissions != null && permissions.contains(permissionName)) {
-                            newFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-                        } else {
-                            newFlags &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-                        }
-                    }
-                    break;
-                    case FLAG_PERMISSION_WHITELIST_UPGRADE: {
-                        mask |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-                        if (permissions != null && permissions.contains(permissionName)) {
-                            newFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-                        } else {
-                            newFlags &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-                        }
-                    }
-                    break;
-                    case FLAG_PERMISSION_WHITELIST_INSTALLER: {
-                        mask |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-                        if (permissions != null && permissions.contains(permissionName)) {
-                            newFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-                        } else {
-                            newFlags &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-                        }
-                    }
-                    break;
-                }
-            }
-
-            if (oldFlags == newFlags) {
-                continue;
-            }
-
-            updatePermissions = true;
-
-            final boolean wasAllowlisted = (oldFlags
-                    & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
-            final boolean isAllowlisted = (newFlags
-                    & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
-
-            // If the permission is policy fixed as granted but it is no longer
-            // on any of the allowlists we need to clear the policy fixed flag
-            // as allowlisting trumps policy i.e. policy cannot grant a non
-            // grantable permission.
-            if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                if (!isAllowlisted && isGranted) {
-                    mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-                    newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-                }
-            }
-
-            // If we are allowlisting an app that does not support runtime permissions
-            // we need to make sure it goes through the permission review UI at launch.
-            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M
-                    && !wasAllowlisted && isAllowlisted) {
-                mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-                newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-            }
-
-            updatePermissionFlagsInternal(pkg.getPackageName(), permissionName, mask, newFlags,
-                    myUid, userId, false, null /*callback*/);
-        }
-
-        if (updatePermissions) {
-            // Update permission of this app to take into account the new allowlist state.
-            restorePermissionState(pkg, false, pkg.getPackageName(), mDefaultPermissionCallback,
-                    userId);
-
-            // If this resulted in losing a permission we need to kill the app.
-            if (oldGrantedRestrictedPermissions == null) {
-                return;
-            }
-
-            final int oldGrantedCount = oldGrantedRestrictedPermissions.size();
-            for (int j = 0; j < oldGrantedCount; j++) {
-                final String permissionName = oldGrantedRestrictedPermissions.valueAt(j);
-                // Sometimes we create a new permission state instance during update.
-                final boolean isGranted;
-                synchronized (mLock) {
-                    final UidPermissionState uidState = getUidStateLocked(pkg, userId);
-                    if (uidState == null) {
-                        Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
-                                + " and user " + userId);
-                        continue;
-                    }
-                    isGranted = uidState.isPermissionGranted(permissionName);
-                }
-                if (!isGranted) {
-                    mDefaultPermissionCallback.onPermissionRevoked(pkg.getUid(), userId, null);
-                    break;
-                }
-            }
-        }
-    }
-
-    private void revokeSharedUserPermissionsForLeavingPackageInternal(
-            @Nullable AndroidPackage pkg, int appId, @NonNull List<AndroidPackage> sharedUserPkgs,
-            @UserIdInt int userId) {
-        if (pkg == null) {
-            Slog.i(TAG, "Trying to update info for null package. Just ignoring");
-            return;
-        }
-
-        // No shared user packages
-        if (sharedUserPkgs.isEmpty()) {
-            return;
-        }
-
-        PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
-                pkg.getPackageName());
-        boolean isShadowingSystemPkg = disabledPs != null && disabledPs.getAppId() == pkg.getUid();
-
-        boolean shouldKillUid = false;
-        // Update permissions
-        for (String eachPerm : pkg.getRequestedPermissions()) {
-            // Check if another package in the shared user needs the permission.
-            boolean used = false;
-            for (AndroidPackage sharedUserpkg : sharedUserPkgs) {
-                if (sharedUserpkg != null
-                        && !sharedUserpkg.getPackageName().equals(pkg.getPackageName())
-                        && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) {
-                    used = true;
-                    break;
-                }
-            }
-            if (used) {
-                continue;
-            }
-
-            // If the package is shadowing a disabled system package,
-            // do not drop permissions that the shadowed package requests.
-            if (isShadowingSystemPkg
-                    && disabledPs.getPkg().getRequestedPermissions().contains(eachPerm)) {
-                continue;
-            }
-
-            synchronized (mLock) {
-                UidPermissionState uidState = getUidStateLocked(appId, userId);
-                if (uidState == null) {
-                    Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
-                            + " and user " + userId);
-                    continue;
-                }
-
-                Permission bp = mRegistry.getPermission(eachPerm);
-                if (bp == null) {
-                    continue;
-                }
-
-                // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any
-                //  permission change?
-                if (uidState.removePermissionState(bp.getName()) && bp.hasGids()) {
-                    shouldKillUid = true;
-                }
-            }
-        }
-
-        // If gids changed, kill all affected packages.
-        if (shouldKillUid) {
-            mHandler.post(() -> {
-                // This has to happen with no lock held.
-                killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
-            });
-        }
-    }
-
-    @GuardedBy("mLock")
-    private boolean revokeUnusedSharedUserPermissionsLocked(
-            List<AndroidPackage> pkgList, UidPermissionState uidState) {
-        // Collect all used permissions in the UID
-        final ArraySet<String> usedPermissions = new ArraySet<>();
-        if (pkgList == null || pkgList.size() == 0) {
-            return false;
-        }
-        for (AndroidPackage pkg : pkgList) {
-            if (pkg.getRequestedPermissions().isEmpty()) {
-                continue;
-            }
-            final int requestedPermCount = pkg.getRequestedPermissions().size();
-            for (int j = 0; j < requestedPermCount; j++) {
-                String permission = pkg.getRequestedPermissions().get(j);
-                Permission bp = mRegistry.getPermission(permission);
-                if (bp != null) {
-                    usedPermissions.add(permission);
-                }
-            }
-        }
-
-        boolean runtimePermissionChanged = false;
-
-        // Prune permissions
-        final List<PermissionState> permissionStates = uidState.getPermissionStates();
-        final int permissionStatesSize = permissionStates.size();
-        for (int i = permissionStatesSize - 1; i >= 0; i--) {
-            PermissionState permissionState = permissionStates.get(i);
-            if (!usedPermissions.contains(permissionState.getName())) {
-                Permission bp = mRegistry.getPermission(permissionState.getName());
-                if (bp != null) {
-                    if (uidState.removePermissionState(bp.getName()) && bp.isRuntime()) {
-                        runtimePermissionChanged = true;
-                    }
-                }
-            }
-        }
-
-        return runtimePermissionChanged;
-    }
-
-    /**
-     * Update permissions when a package changed.
-     *
-     * <p><ol>
-     *     <li>Reconsider the ownership of permission</li>
-     *     <li>Update the state (grant, flags) of the permissions</li>
-     * </ol>
-     *
-     * @param packageName The package that is updated
-     * @param pkg The package that is updated, or {@code null} if package is deleted
-     */
-    private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) {
-        // If the package is being deleted, update the permissions of all the apps
-        final int flags =
-                (pkg == null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG
-                        : UPDATE_PERMISSIONS_REPLACE_PKG);
-        updatePermissions(
-                packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback);
-    }
-
-    /**
-     * Update all permissions for all apps.
-     *
-     * <p><ol>
-     *     <li>Reconsider the ownership of permission</li>
-     *     <li>Update the state (grant, flags) of the permissions</li>
-     * </ol>
-     *
-     * @param volumeUuid The volume UUID of the packages to be updated
-     * @param fingerprintChanged whether the current build fingerprint is different from what it was
-     *                           when this volume was last mounted
-     */
-    private void updateAllPermissions(@NonNull String volumeUuid, boolean fingerprintChanged) {
-        PackageManager.corkPackageInfoCache();  // Prevent invalidation storm
-        try {
-            final int flags = UPDATE_PERMISSIONS_ALL |
-                    (fingerprintChanged
-                            ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
-                            : 0);
-            updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback);
-        } finally {
-            PackageManager.uncorkPackageInfoCache();
-        }
-    }
-
-    /**
-     * Update all packages on the volume, <u>beside</u> the changing package. If the changing
-     * package is set too, all packages are updated.
-     */
-    private static final int UPDATE_PERMISSIONS_ALL = 1 << 0;
-    /** The changing package is replaced. Requires the changing package to be set */
-    private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1 << 1;
-    /**
-     * Schedule all packages <u>beside</u> the changing package for replacement. Requires
-     * UPDATE_PERMISSIONS_ALL to be set
-     */
-    private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1 << 2;
-
-    @IntDef(flag = true, prefix = { "UPDATE_PERMISSIONS_" }, value = {
-            UPDATE_PERMISSIONS_ALL, UPDATE_PERMISSIONS_REPLACE_PKG,
-            UPDATE_PERMISSIONS_REPLACE_ALL })
-    @Retention(RetentionPolicy.SOURCE)
-    private @interface UpdatePermissionFlags {}
-
-    /**
-     * Update permissions when packages changed.
-     *
-     * <p><ol>
-     *     <li>Reconsider the ownership of permission</li>
-     *     <li>Update the state (grant, flags) of the permissions</li>
-     * </ol>
-     *
-     * <p>Meaning of combination of package parameters:
-     * <table>
-     *     <tr><th></th><th>changingPkgName != null</th><th>changingPkgName == null</th></tr>
-     *     <tr><th>changingPkg != null</th><td>package is updated</td><td>invalid</td></tr>
-     *     <tr><th>changingPkg == null</th><td>package is deleted</td><td>all packages are
-     *                                                                    updated</td></tr>
-     * </table>
-     *
-     * @param changingPkgName The package that is updated, or {@code null} if all packages should be
-     *                    updated
-     * @param changingPkg The package that is updated, or {@code null} if all packages should be
-     *                    updated or package is deleted
-     * @param replaceVolumeUuid The volume of the packages to be updated are on, {@code null} for
-     *                          all volumes
-     * @param flags Control permission for which apps should be updated
-     * @param callback Callback to call after permission changes
-     */
-    private void updatePermissions(final @Nullable String changingPkgName,
-            final @Nullable AndroidPackage changingPkg,
-            final @Nullable String replaceVolumeUuid,
-            @UpdatePermissionFlags int flags,
-            final @Nullable PermissionCallback callback) {
-        // TODO: Most of the methods exposing BasePermission internals [source package name,
-        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
-        // have package settings, we should make note of it elsewhere [map between
-        // source package name and BasePermission] and cycle through that here. Then we
-        // define a single method on BasePermission that takes a PackageSetting, changing
-        // package name and a package.
-        // NOTE: With this approach, we also don't need to tree trees differently than
-        // normal permissions. Today, we need two separate loops because these BasePermission
-        // objects are stored separately.
-        // Make sure there are no dangling permission trees.
-        boolean permissionTreesSourcePackageChanged = updatePermissionTreeSourcePackage(
-                changingPkgName, changingPkg);
-        // Make sure all dynamic permissions have been assigned to a package,
-        // and make sure there are no dangling permissions.
-        boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName,
-                callback);
-
-        if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) {
-            // Permission ownership has changed. This e.g. changes which packages can get signature
-            // permissions
-            Slog.i(TAG, "Permission ownership changed. Updating all permissions.");
-            flags |= UPDATE_PERMISSIONS_ALL;
-        }
-
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "restorePermissionState");
-        // Now update the permissions for all packages.
-        if ((flags & UPDATE_PERMISSIONS_ALL) != 0) {
-            final boolean replaceAll = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0);
-            mPackageManagerInt.forEachPackage((AndroidPackage pkg) -> {
-                if (pkg == changingPkg) {
-                    return;
-                }
-                // Only replace for packages on requested volume
-                final String volumeUuid = getVolumeUuidForPackage(pkg);
-                final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid);
-                restorePermissionState(pkg, replace, changingPkgName, callback,
-                        UserHandle.USER_ALL);
-            });
-        }
-
-        if (changingPkg != null) {
-            // Only replace for packages on requested volume
-            final String volumeUuid = getVolumeUuidForPackage(changingPkg);
-            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
-                    && Objects.equals(replaceVolumeUuid, volumeUuid);
-            restorePermissionState(changingPkg, replace, changingPkgName, callback,
-                    UserHandle.USER_ALL);
-        }
-        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-    }
-
-    /**
-     * Update which app declares a permission.
-     *
-     * @param packageName The package that is updated, or {@code null} if all packages should be
-     *                    updated
-     *
-     * @return {@code true} if a permission source package might have changed
-     */
-    private boolean updatePermissionSourcePackage(@Nullable String packageName,
-            final @Nullable PermissionCallback callback) {
-        // Always need update if packageName is null
-        if (packageName == null) {
-            return true;
-        }
-
-        boolean changed = false;
-        Set<Permission> needsUpdate = null;
-        synchronized (mLock) {
-            for (final Permission bp : mRegistry.getPermissions()) {
-                if (bp.isDynamic()) {
-                    bp.updateDynamicPermission(mRegistry.getPermissionTrees());
-                }
-                if (!packageName.equals(bp.getPackageName())) {
-                    // Not checking sourcePackageSetting because it can be null when
-                    // the permission source package is the target package and the target package is
-                    // being uninstalled,
-                    continue;
-                }
-                // The target package is the source of the current permission
-                // Set to changed for either install or uninstall
-                changed = true;
-                if (needsUpdate == null) {
-                    needsUpdate = new ArraySet<>();
-                }
-                needsUpdate.add(bp);
-            }
-        }
-        if (needsUpdate != null) {
-            final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
-            for (final Permission bp : needsUpdate) {
-                // If the target package is being uninstalled, we need to revoke this permission
-                // From all other packages
-                if (pkg == null || !hasPermission(pkg, bp.getName())) {
-                    if (!isPermissionDeclaredByDisabledSystemPkg(bp)) {
-                        Slog.i(TAG, "Removing permission " + bp.getName()
-                                + " that used to be declared by " + bp.getPackageName());
-                        if (bp.isRuntime()) {
-                            final int[] userIds = mUserManagerInt.getUserIds();
-                            final int numUserIds = userIds.length;
-                            for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
-                                final int userId = userIds[userIdNum];
-                                mPackageManagerInt.forEachPackage((AndroidPackage p) ->
-                                        revokePermissionFromPackageForUser(p.getPackageName(),
-                                                bp.getName(), true, userId, callback));
-                            }
-                        } else {
-                            mPackageManagerInt.forEachPackage(p -> {
-                                final int[] userIds = mUserManagerInt.getUserIds();
-                                synchronized (mLock) {
-                                    for (final int userId : userIds) {
-                                        final UidPermissionState uidState = getUidStateLocked(p,
-                                                userId);
-                                        if (uidState == null) {
-                                            Slog.e(TAG, "Missing permissions state for "
-                                                    + p.getPackageName() + " and user " + userId);
-                                            continue;
-                                        }
-                                        uidState.removePermissionState(bp.getName());
-                                    }
-                                }
-                            });
-                        }
-                    }
-                    synchronized (mLock) {
-                        mRegistry.removePermission(bp.getName());
-                    }
-                    continue;
-                }
-                final AndroidPackage sourcePkg =
-                        mPackageManagerInt.getPackage(bp.getPackageName());
-                final PackageStateInternal sourcePs =
-                        mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
-                synchronized (mLock) {
-                    if (sourcePkg != null && sourcePs != null) {
-                        continue;
-                    }
-                    Slog.w(TAG, "Removing dangling permission: " + bp.getName()
-                            + " from package " + bp.getPackageName());
-                    mRegistry.removePermission(bp.getName());
-                }
-            }
-        }
-        return changed;
-    }
-
-    private boolean isPermissionDeclaredByDisabledSystemPkg(@NonNull Permission permission) {
-        final PackageSetting disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage(
-                    permission.getPackageName());
-        if (disabledSourcePs != null && disabledSourcePs.getPkg() != null) {
-            final String permissionName = permission.getName();
-            final List<ParsedPermission> sourcePerms = disabledSourcePs.getPkg().getPermissions();
-            for (ParsedPermission sourcePerm : sourcePerms) {
-                if (TextUtils.equals(permissionName, sourcePerm.getName())
-                        && permission.getProtectionLevel() == sourcePerm.getProtectionLevel()) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Revoke a runtime permission from a package for a given user ID.
-     */
-    private void revokePermissionFromPackageForUser(@NonNull String pName,
-            @NonNull String permissionName, boolean overridePolicy, int userId,
-            @Nullable PermissionCallback callback) {
-        final ApplicationInfo appInfo =
-                mPackageManagerInt.getApplicationInfo(pName, 0,
-                        Process.SYSTEM_UID, UserHandle.USER_SYSTEM);
-        if (appInfo != null
-                && appInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-            return;
-        }
-
-        if (checkPermissionImpl(pName, permissionName, userId)
-                == PackageManager.PERMISSION_GRANTED) {
-            try {
-                revokeRuntimePermissionInternal(
-                        pName, permissionName,
-                        overridePolicy,
-                        Process.SYSTEM_UID,
-                        userId,
-                        null, callback);
-            } catch (IllegalArgumentException e) {
-                Slog.e(TAG,
-                        "Failed to revoke "
-                                + permissionName
-                                + " from "
-                                + pName,
-                        e);
-            }
-        }
-    }
-    /**
-     * Update which app owns a permission trees.
-     *
-     * <p>Possible parameter combinations
-     * <table>
-     *     <tr><th></th><th>packageName != null</th><th>packageName == null</th></tr>
-     *     <tr><th>pkg != null</th><td>package is updated</td><td>invalid</td></tr>
-     *     <tr><th>pkg == null</th><td>package is deleted</td><td>all packages are updated</td></tr>
-     * </table>
-     *
-     * @param packageName The package that is updated, or {@code null} if all packages should be
-     *                    updated
-     * @param pkg The package that is updated, or {@code null} if all packages should be updated or
-     *            package is deleted
-     *
-     * @return {@code true} if a permission tree ownership might have changed
-     */
-    private boolean updatePermissionTreeSourcePackage(@Nullable String packageName,
-            @Nullable AndroidPackage pkg) {
-        // Always need update if packageName is null
-        if (packageName == null) {
-            return true;
-        }
-        boolean changed = false;
-
-        Set<Permission> needsUpdate = null;
-        synchronized (mLock) {
-            final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator();
-            while (it.hasNext()) {
-                final Permission bp = it.next();
-                if (!packageName.equals(bp.getPackageName())) {
-                    // Not checking sourcePackageSetting because it can be null when
-                    // the permission source package is the target package and the target package is
-                    // being uninstalled,
-                    continue;
-                }
-                // The target package is the source of the current permission tree
-                // Set to changed for either install or uninstall
-                changed = true;
-                if (pkg == null || !hasPermission(pkg, bp.getName())) {
-                    Slog.i(TAG, "Removing permission tree " + bp.getName()
-                            + " that used to be declared by " + bp.getPackageName());
-                    it.remove();
-                }
-                if (needsUpdate == null) {
-                    needsUpdate = new ArraySet<>();
-                }
-                needsUpdate.add(bp);
-            }
-        }
-        if (needsUpdate != null) {
-            for (final Permission bp : needsUpdate) {
-                final AndroidPackage sourcePkg =
-                        mPackageManagerInt.getPackage(bp.getPackageName());
-                final PackageStateInternal sourcePs =
-                        mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
-                synchronized (mLock) {
-                    if (sourcePkg != null && sourcePs != null) {
-                        continue;
-                    }
-                    Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
-                            + " from package " + bp.getPackageName());
-                    mRegistry.removePermission(bp.getName());
-                }
-            }
-        }
-        return changed;
-    }
-
-    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED
-            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(message + " requires "
-                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
-                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
-        }
-    }
-
-    private void enforceGrantRevokeGetRuntimePermissionPermissions(@NonNull String message) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED
-            && mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED
-            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(message + " requires "
-                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
-                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + " or "
-                    + Manifest.permission.GET_RUNTIME_PERMISSIONS);
-        }
-    }
-
-    /**
-     * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
-     * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller.
-     *
-     * @param checkShell whether to prevent shell from access if there's a debugging restriction
-     * @param message the message to log on security exception
-     */
-    private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
-            boolean requireFullPermission, boolean checkShell, @Nullable String message) {
-        if (userId < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userId);
-        }
-        if (checkShell) {
-            enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-        }
-        final int callingUserId = UserHandle.getUserId(callingUid);
-        if (checkCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission)) {
-            return;
-        }
-        String errorMessage = buildInvalidCrossUserPermissionMessage(
-                callingUid, userId, message, requireFullPermission);
-        Slog.w(TAG, errorMessage);
-        throw new SecurityException(errorMessage);
-    }
-
-    /**
-     *  Enforces that if the caller is shell, it does not have the provided user restriction.
-     */
-    private void enforceShellRestriction(@NonNull String restriction, int callingUid,
-            @UserIdInt int userId) {
-        if (callingUid == Process.SHELL_UID) {
-            if (userId >= 0 && mUserManagerInt.hasUserRestriction(restriction, userId)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + userId);
-            } else if (userId < 0) {
-                Slog.e(LOG_TAG, "Unable to check shell permission for user "
-                        + userId + "\n\t" + Debug.getCallers(3));
-            }
-        }
-    }
-
-    private boolean checkCrossUserPermission(int callingUid, @UserIdInt int callingUserId,
-            @UserIdInt int userId, boolean requireFullPermission) {
-        if (userId == callingUserId) {
-            return true;
-        }
-        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
-            return true;
-        }
-        if (requireFullPermission) {
-            return checkCallingOrSelfPermission(
-                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        }
-        return checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-                || checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS);
-    }
-
-    private boolean checkCallingOrSelfPermission(String permission) {
-        return mContext.checkCallingOrSelfPermission(permission)
-                == PackageManager.PERMISSION_GRANTED;
-    }
-
-    @NonNull
-    private static String buildInvalidCrossUserPermissionMessage(int callingUid,
-            @UserIdInt int userId, @Nullable String message, boolean requireFullPermission) {
-        StringBuilder builder = new StringBuilder();
-        if (message != null) {
-            builder.append(message);
-            builder.append(": ");
-        }
-        builder.append("UID ");
-        builder.append(callingUid);
-        builder.append(" requires ");
-        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-        if (!requireFullPermission) {
-            builder.append(" or ");
-            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
-        }
-        builder.append(" to access user ");
-        builder.append(userId);
-        builder.append(".");
-        return builder.toString();
-    }
-
-    @GuardedBy("mLock")
-    private int calculateCurrentPermissionFootprintLocked(@NonNull Permission permissionTree) {
-        int size = 0;
-        for (final Permission permission : mRegistry.getPermissions()) {
-            size += permissionTree.calculateFootprint(permission);
-        }
-        return size;
-    }
-
-    @GuardedBy("mLock")
-    private void enforcePermissionCapLocked(PermissionInfo info, Permission tree) {
-        // We calculate the max size of permissions defined by this uid and throw
-        // if that plus the size of 'info' would exceed our stated maximum.
-        if (tree.getUid() != Process.SYSTEM_UID) {
-            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
-            if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) {
-                throw new SecurityException("Permission tree size cap exceeded");
-            }
-        }
-    }
-
-    private void systemReady() {
-        // Now that we've scanned all packages, and granted any default
-        // permissions, ensure permissions are updated. Beware of dragons if you
-        // try optimizing this.
-        updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false);
-
-        final PermissionPolicyInternal permissionPolicyInternal = LocalServices.getService(
-                PermissionPolicyInternal.class);
-        permissionPolicyInternal.setOnInitializedCallback(userId ->
-                // The SDK updated case is already handled when we run during the ctor.
-                updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false)
-        );
-
-        mSystemReady = true;
-
-        synchronized (mLock) {
-            if (mPrivappPermissionsViolations != null) {
-                throw new IllegalStateException("Signature|privileged permissions not in "
-                        + "privapp-permissions allowlist: " + mPrivappPermissionsViolations);
-            }
-        }
-
-        mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
-        mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
-    }
-
-    private static String getVolumeUuidForPackage(AndroidPackage pkg) {
-        if (pkg == null) {
-            return StorageManager.UUID_PRIVATE_INTERNAL;
-        }
-        if (pkg.isExternalStorage()) {
-            if (TextUtils.isEmpty(pkg.getVolumeUuid())) {
-                return StorageManager.UUID_PRIMARY_PHYSICAL;
-            } else {
-                return pkg.getVolumeUuid();
-            }
-        } else {
-            return StorageManager.UUID_PRIVATE_INTERNAL;
-        }
-    }
-
-    private static boolean hasPermission(AndroidPackage pkg, String permName) {
-        if (pkg.getPermissions().isEmpty()) {
-            return false;
-        }
-
-        for (int i = pkg.getPermissions().size() - 1; i >= 0; i--) {
-            if (pkg.getPermissions().get(i).getName().equals(permName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Log that a permission request was granted/revoked.
-     *
-     * @param action the action performed
-     * @param name name of the permission
-     * @param packageName package permission is for
-     */
-    private void logPermission(int action, @NonNull String name, @NonNull String packageName) {
-        final LogMaker log = new LogMaker(action);
-        log.setPackageName(packageName);
-        log.addTaggedData(MetricsEvent.FIELD_PERMISSION, name);
-
-        mMetricsLogger.write(log);
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private UidPermissionState getUidStateLocked(@NonNull PackageStateInternal ps,
-            @UserIdInt int userId) {
-        return getUidStateLocked(ps.getAppId(), userId);
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private UidPermissionState getUidStateLocked(@NonNull AndroidPackage pkg,
-            @UserIdInt int userId) {
-        return getUidStateLocked(pkg.getUid(), userId);
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private UidPermissionState getUidStateLocked(@AppIdInt int appId, @UserIdInt int userId) {
-        final UserPermissionState userState = mState.getUserState(userId);
-        if (userState == null) {
+    @Override
+    public ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName, int flags) {
+        List<PermissionInfo> permissionInfo =
+                mPermissionManagerServiceImpl.queryPermissionsByGroup(groupName, flags);
+        if (permissionInfo == null) {
             return null;
         }
-        return userState.getUidState(appId);
+
+        return new ParceledListSlice<>(permissionInfo);
     }
 
-    private void removeUidStateAndResetPackageInstallPermissionsFixed(@AppIdInt int appId,
-            @NonNull String packageName, @UserIdInt int userId) {
-        synchronized (mLock) {
-            final UserPermissionState userState = mState.getUserState(userId);
-            if (userState == null) {
-                return;
-            }
-            userState.removeUidState(appId);
-            userState.setInstallPermissionsFixed(packageName, false);
-        }
+    @Override
+    public boolean addPermission(PermissionInfo permissionInfo, boolean async) {
+        return mPermissionManagerServiceImpl.addPermission(permissionInfo, async);
     }
 
-    private void readLegacyPermissionState() {
-        final int[] userIds = getAllUserIds();
-        mPackageManagerInt.forEachPackageSetting(ps -> {
-            final int appId = ps.getAppId();
-            final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
-
-            synchronized (mLock) {
-                for (final int userId : userIds) {
-                    final UserPermissionState userState = mState.getOrCreateUserState(userId);
-
-                    userState.setInstallPermissionsFixed(ps.getPackageName(),
-                            ps.isInstallPermissionsFixed());
-                    final UidPermissionState uidState = userState.getOrCreateUidState(appId);
-                    uidState.reset();
-                    uidState.setMissing(legacyState.isMissing(userId));
-                    readLegacyPermissionStatesLocked(uidState,
-                            legacyState.getPermissionStates(userId));
-                }
-            }
-        });
+    @Override
+    public void removePermission(String permissionName) {
+        mPermissionManagerServiceImpl.removePermission(permissionName);
     }
 
-    @GuardedBy("mLock")
-    private void readLegacyPermissionStatesLocked(@NonNull UidPermissionState uidState,
-            @NonNull Collection<LegacyPermissionState.PermissionState> permissionStates) {
-        for (final LegacyPermissionState.PermissionState permissionState : permissionStates) {
-            final String permissionName = permissionState.getName();
-            final Permission permission = mRegistry.getPermission(permissionName);
-            if (permission == null) {
-                Slog.w(TAG, "Unknown permission: " + permissionName);
-                continue;
-            }
-            uidState.putPermissionState(permission, permissionState.isGranted(),
-                    permissionState.getFlags());
-        }
+    @Override
+    public int getPermissionFlags(String packageName, String permissionName, int userId) {
+        return mPermissionManagerServiceImpl
+                .getPermissionFlags(packageName, permissionName, userId);
     }
 
-    private void writeLegacyPermissionState() {
-        final int[] userIds;
-        synchronized (mLock) {
-            userIds = mState.getUserIds();
-        }
-        mPackageManagerInt.forEachPackageSetting(ps -> {
-            ps.setInstallPermissionsFixed(false);
-            final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
-            legacyState.reset();
-            final int appId = ps.getAppId();
-
-            synchronized (mLock) {
-                for (final int userId : userIds) {
-                    final UserPermissionState userState = mState.getUserState(userId);
-                    if (userState == null) {
-                        Slog.e(TAG, "Missing user state for " + userId);
-                        continue;
-                    }
-
-                    if (userState.areInstallPermissionsFixed(ps.getPackageName())) {
-                        ps.setInstallPermissionsFixed(true);
-                    }
-
-                    final UidPermissionState uidState = userState.getUidState(appId);
-                    if (uidState == null) {
-                        Slog.e(TAG, "Missing permission state for " + ps.getPackageName()
-                                + " and user " + userId);
-                        continue;
-                    }
-
-                    legacyState.setMissing(uidState.isMissing(), userId);
-                    final List<PermissionState> permissionStates = uidState.getPermissionStates();
-                    final int permissionStatesSize = permissionStates.size();
-                    for (int i = 0; i < permissionStatesSize; i++) {
-                        final PermissionState permissionState = permissionStates.get(i);
-
-                        final LegacyPermissionState.PermissionState legacyPermissionState =
-                                new LegacyPermissionState.PermissionState(permissionState.getName(),
-                                        permissionState.getPermission().isRuntime(),
-                                        permissionState.isGranted(), permissionState.getFlags());
-                        legacyState.putPermissionState(legacyPermissionState, userId);
-                    }
-                }
-            }
-        });
+    @Override
+    public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+        mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
+                flagValues, checkAdjustPolicyFlagPermission, userId);
     }
 
-    private void readLegacyPermissions(@NonNull LegacyPermissionSettings legacyPermissionSettings) {
-        for (int readPermissionOrPermissionTree = 0; readPermissionOrPermissionTree < 2;
-                readPermissionOrPermissionTree++) {
-            final List<LegacyPermission> legacyPermissions = readPermissionOrPermissionTree == 0
-                    ? legacyPermissionSettings.getPermissions()
-                    : legacyPermissionSettings.getPermissionTrees();
-            synchronized (mLock) {
-                final int legacyPermissionsSize = legacyPermissions.size();
-                for (int i = 0; i < legacyPermissionsSize; i++) {
-                    final LegacyPermission legacyPermission = legacyPermissions.get(i);
-                    final Permission permission = new Permission(
-                            legacyPermission.getPermissionInfo(), legacyPermission.getType());
-                    if (readPermissionOrPermissionTree == 0) {
-                        // Config permissions are currently read in PermissionManagerService
-                        // constructor. The old behavior was to add other attributes to the config
-                        // permission in LegacyPermission.read(), so equivalently we can add the
-                        // GIDs to the new permissions here, since config permissions created in
-                        // PermissionManagerService constructor get only their names and GIDs there.
-                        final Permission configPermission = mRegistry.getPermission(
-                                permission.getName());
-                        if (configPermission != null
-                                && configPermission.getType() == Permission.TYPE_CONFIG) {
-                            permission.setGids(configPermission.getRawGids(),
-                                    configPermission.areGidsPerUser());
-                        }
-                        mRegistry.addPermission(permission);
-                    } else {
-                        mRegistry.addPermissionTree(permission);
-                    }
-                }
-            }
-        }
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
+        mPermissionManagerServiceImpl.updatePermissionFlagsForAllApps(flagMask, flagValues, userId);
     }
 
-    private void writeLegacyPermissions(
-            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
-        for (int writePermissionOrPermissionTree = 0; writePermissionOrPermissionTree < 2;
-                writePermissionOrPermissionTree++) {
-            final List<LegacyPermission> legacyPermissions = new ArrayList<>();
-            synchronized (mLock) {
-                final Collection<Permission> permissions = writePermissionOrPermissionTree == 0
-                        ? mRegistry.getPermissions() : mRegistry.getPermissionTrees();
-                for (final Permission permission : permissions) {
-                    // We don't need to provide UID and GIDs, which are only retrieved when dumping.
-                    final LegacyPermission legacyPermission = new LegacyPermission(
-                            permission.getPermissionInfo(), permission.getType(), 0,
-                            EmptyArray.INT);
-                    legacyPermissions.add(legacyPermission);
-                }
-            }
-            if (writePermissionOrPermissionTree == 0) {
-                legacyPermissionSettings.replacePermissions(legacyPermissions);
-            } else {
-                legacyPermissionSettings.replacePermissionTrees(legacyPermissions);
-            }
-        }
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        mPermissionManagerServiceImpl.addOnPermissionsChangeListener(listener);
     }
 
-    private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp,
-            @Nullable AndroidPackage oldPkg) {
-        if (!pkg.getAdoptPermissions().isEmpty()) {
-            // This package wants to adopt ownership of permissions from
-            // another package.
-            for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) {
-                final String origName = pkg.getAdoptPermissions().get(i);
-                if (canAdoptPermissionsInternal(origName, pkg)) {
-                    Slog.i(TAG, "Adopting permissions from " + origName + " to "
-                            + pkg.getPackageName());
-                    synchronized (mLock) {
-                        mRegistry.transferPermissions(origName, pkg.getPackageName());
-                    }
-                }
-            }
-        }
-
-        // Don't allow ephemeral applications to define new permissions groups.
-        if (isInstantApp) {
-            Slog.w(TAG, "Permission groups from package " + pkg.getPackageName()
-                    + " ignored: instant apps cannot define new permission groups.");
-        } else {
-            addAllPermissionGroupsInternal(pkg);
-        }
-
-        // If a permission has had its defining app changed, or it has had its protection
-        // upgraded, we need to revoke apps that hold it
-        final List<String> permissionsWithChangedDefinition;
-        // Don't allow ephemeral applications to define new permissions.
-        if (isInstantApp) {
-            permissionsWithChangedDefinition = null;
-            Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
-                    + " ignored: instant apps cannot define new permissions.");
-        } else {
-            permissionsWithChangedDefinition = addAllPermissionsInternal(pkg);
-        }
-
-        boolean hasOldPkg = oldPkg != null;
-        boolean hasPermissionDefinitionChanges =
-                !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
-        if (hasOldPkg || hasPermissionDefinitionChanges) {
-            // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
-            // revoke callbacks from this method might need to kill apps which need the
-            // mPackages lock on a different thread. This would dead lock.
-            AsyncTask.execute(() -> {
-                if (hasOldPkg) {
-                    revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg);
-                    revokeStoragePermissionsIfScopeExpandedInternal(pkg, oldPkg);
-                }
-                if (hasPermissionDefinitionChanges) {
-                    revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
-                            permissionsWithChangedDefinition);
-                }
-            });
-        }
+    @Override
+    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        mPermissionManagerServiceImpl.removeOnPermissionsChangeListener(listener);
     }
 
-    private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName,
-            @NonNull AndroidPackage newPkg) {
-        final PackageStateInternal oldPs =
-                mPackageManagerInt.getPackageStateInternal(oldPackageName);
-        if (oldPs == null) {
-            return false;
-        }
-        if (!oldPs.isSystem()) {
-            Slog.w(TAG, "Unable to update from " + oldPs.getPackageName()
-                    + " to " + newPkg.getPackageName()
-                    + ": old package not in system partition");
-            return false;
-        }
-        if (mPackageManagerInt.getPackage(oldPs.getPackageName()) != null) {
-            Slog.w(TAG, "Unable to update from " + oldPs.getPackageName()
-                    + " to " + newPkg.getPackageName()
-                    + ": old package still exists");
-            return false;
-        }
-        return true;
+    @Override
+    public List<String> getAllowlistedRestrictedPermissions(String packageName,
+            int flags, int userId) {
+        return mPermissionManagerServiceImpl.getAllowlistedRestrictedPermissions(packageName,
+                flags, userId);
     }
 
-    private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId,
-            @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
-            @UserIdInt int[] userIds) {
-        // If previousAppId is not Process.INVALID_UID, the package is performing a migration out
-        // of a shared user group. Operations we need to do before calling updatePermissions():
-        // - Retrieve the original uid permission state and create a copy of it as the new app's
-        //   uid state. The new permission state will be properly updated in updatePermissions().
-        // - Remove the app from the original shared user group. Other apps in the shared
-        //   user group will perceive as if the original app is uninstalled.
-        if (previousAppId != Process.INVALID_UID) {
-            final PackageStateInternal ps =
-                    mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
-            final List<AndroidPackage> origSharedUserPackages =
-                    mPackageManagerInt.getPackagesForAppId(previousAppId);
-
-            synchronized (mLock) {
-                // All users are affected
-                for (final int userId : getAllUserIds()) {
-                    // Retrieve the original uid state
-                    final UserPermissionState userState = mState.getUserState(userId);
-                    if (userState == null) {
-                        continue;
-                    }
-                    final UidPermissionState prevUidState = userState.getUidState(previousAppId);
-                    if (prevUidState == null) {
-                        continue;
-                    }
-
-                    // Insert new uid state by cloning the original one
-                    userState.createUidStateWithExisting(ps.getAppId(), prevUidState);
-
-                    // Remove original app ID from original shared user group
-                    // Should match the implementation of onPackageUninstalledInternal(...)
-                    if (origSharedUserPackages.isEmpty()) {
-                        removeUidStateAndResetPackageInstallPermissionsFixed(
-                                previousAppId, pkg.getPackageName(), userId);
-                    } else {
-                        revokeSharedUserPermissionsForLeavingPackageInternal(pkg, previousAppId,
-                                origSharedUserPackages, userId);
-                    }
-                }
-            }
-        }
-        updatePermissions(pkg.getPackageName(), pkg);
-        for (final int userId : userIds) {
-            addAllowlistedRestrictedPermissionsInternal(pkg,
-                    params.getAllowlistedRestrictedPermissions(),
-                    FLAG_PERMISSION_WHITELIST_INSTALLER, userId);
-            final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode();
-            if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED
-                    || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) {
-                setAutoRevokeExemptedInternal(pkg,
-                        autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId);
-            }
-            grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId);
-        }
+    @Override
+    public boolean addAllowlistedRestrictedPermission(String packageName, String permissionName,
+            int flags, int userId) {
+        return mPermissionManagerServiceImpl.addAllowlistedRestrictedPermission(packageName,
+                permissionName, flags, userId);
     }
 
-    private void addAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
-            @NonNull List<String> allowlistedRestrictedPermissions,
-            @PermissionWhitelistFlags int flags, @UserIdInt int userId) {
-        List<String> permissions = getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId);
-        if (permissions != null) {
-            ArraySet<String> permissionSet = new ArraySet<>(permissions);
-            permissionSet.addAll(allowlistedRestrictedPermissions);
-            permissions = new ArrayList<>(permissionSet);
-        } else {
-            permissions = allowlistedRestrictedPermissions;
-        }
-        setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId);
+    @Override
+    public boolean removeAllowlistedRestrictedPermission(String packageName, String permissionName,
+            int flags, int userId) {
+        return mPermissionManagerServiceImpl.removeAllowlistedRestrictedPermission(packageName,
+                permissionName, flags, userId);
     }
 
-    private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) {
-        removeAllPermissionsInternal(pkg);
+    @Override
+    public void grantRuntimePermission(String packageName, String permissionName, int userId) {
+        mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
     }
 
-    private void onPackageUninstalledInternal(@NonNull String packageName, int appId,
-            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
-            @UserIdInt int[] userIds) {
-        // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin
-        //  a shared UID permission state.
-
-        // TODO: Move these checks to check PackageState to be more reliable.
-        // System packages should always have an available APK.
-        if (pkg != null && pkg.isSystem()
-                // We may be fully removing invalid system packages during boot, and in that case we
-                // do want to remove their permission state. So make sure that the package is only
-                // being marked as uninstalled instead of fully removed.
-                && mPackageManagerInt.getPackage(packageName) != null) {
-            // If we are only marking a system package as uninstalled, we need to keep its
-            // pregranted permission state so that it still works once it gets reinstalled, thus
-            // only reset the user modifications to its permission state.
-            for (final int userId : userIds) {
-                resetRuntimePermissionsInternal(pkg, userId);
-            }
-            return;
-        }
-        updatePermissions(packageName, null);
-        for (final int userId : userIds) {
-            if (sharedUserPkgs.isEmpty()) {
-                removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId);
-            } else {
-                // Remove permissions associated with package. Since runtime
-                // permissions are per user we have to kill the removed package
-                // or packages running under the shared user of the removed
-                // package if revoking the permissions requested only by the removed
-                // package is successful and this causes a change in gids.
-                revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId,
-                        sharedUserPkgs, userId);
-            }
-        }
+    @Override
+    public void revokeRuntimePermission(String packageName, String permissionName, int userId,
+            String reason) {
+        mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName, userId,
+                reason);
     }
 
-    @NonNull
-    private List<LegacyPermission> getLegacyPermissions() {
-        synchronized (mLock) {
-            final List<LegacyPermission> legacyPermissions = new ArrayList<>();
-            for (final Permission permission : mRegistry.getPermissions()) {
-                final LegacyPermission legacyPermission = new LegacyPermission(
-                        permission.getPermissionInfo(), permission.getType(), permission.getUid(),
-                        permission.getRawGids());
-                legacyPermissions.add(legacyPermission);
-            }
-            return legacyPermissions;
-        }
+    @Override
+    public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
+            int userId) {
+        return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
+                permissionName, userId);
     }
 
-    @NonNull
-    private Map<String, Set<String>> getAllAppOpPermissionPackages() {
-        synchronized (mLock) {
-            final ArrayMap<String, ArraySet<String>> appOpPermissionPackages =
-                    mRegistry.getAllAppOpPermissionPackages();
-            final Map<String, Set<String>> deepClone = new ArrayMap<>();
-            final int appOpPermissionPackagesSize = appOpPermissionPackages.size();
-            for (int i = 0; i < appOpPermissionPackagesSize; i++) {
-                final String appOpPermission = appOpPermissionPackages.keyAt(i);
-                final ArraySet<String> packageNames = appOpPermissionPackages.valueAt(i);
-                deepClone.put(appOpPermission, new ArraySet<>(packageNames));
-            }
-            return deepClone;
-        }
+    @Override
+    public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
+            int userId) {
+        return mPermissionManagerServiceImpl
+                .isPermissionRevokedByPolicy(packageName, permissionName, userId);
     }
 
-    @NonNull
-    private LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
-        final LegacyPermissionState legacyState = new LegacyPermissionState();
-        synchronized (mLock) {
-            final int[] userIds = mState.getUserIds();
-            for (final int userId : userIds) {
-                final UidPermissionState uidState = getUidStateLocked(appId, userId);
-                if (uidState == null) {
-                    Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID "
-                            + userId);
-                    continue;
-                }
-
-                final List<PermissionState> permissionStates = uidState.getPermissionStates();
-                final int permissionStatesSize = permissionStates.size();
-                for (int i = 0; i < permissionStatesSize; i++) {
-                    final PermissionState permissionState = permissionStates.get(i);
-
-                    final LegacyPermissionState.PermissionState legacyPermissionState =
-                            new LegacyPermissionState.PermissionState(permissionState.getName(),
-                                    permissionState.getPermission().isRuntime(),
-                                    permissionState.isGranted(), permissionState.getFlags());
-                    legacyState.putPermissionState(legacyPermissionState, userId);
-                }
-            }
-        }
-        return legacyState;
+    @Override
+    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
+        return mPermissionManagerServiceImpl.getSplitPermissions();
     }
 
-    @NonNull
-    private int[] getGidsForUid(int uid) {
-        final int appId = UserHandle.getAppId(uid);
-        final int userId = UserHandle.getUserId(uid);
-        synchronized (mLock) {
-            final UidPermissionState uidState = getUidStateLocked(appId, userId);
-            if (uidState == null) {
-                Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID "
-                        + userId);
-                return EMPTY_INT_ARRAY;
-            }
-            return uidState.computeGids(mGlobalGids, userId);
-        }
-    }
+    /* End of delegate methods to PermissionManagerServiceInterface */
 
     private class PermissionManagerServiceInternalImpl implements PermissionManagerServiceInternal {
         @Override
@@ -5136,140 +583,6 @@
         }
 
         @Override
-        public void onSystemReady() {
-            PermissionManagerService.this.systemReady();
-        }
-
-        @Override
-        public boolean isPermissionsReviewRequired(@NonNull String packageName,
-                @UserIdInt int userId) {
-            Objects.requireNonNull(packageName, "packageName");
-            // TODO(b/173235285): Some caller may pass USER_ALL as userId.
-            //Preconditions.checkArgumentNonnegative(userId, "userId");
-            return isPermissionsReviewRequiredInternal(packageName, userId);
-        }
-
-        @Override
-        public void readLegacyPermissionStateTEMP() {
-            PermissionManagerService.this.readLegacyPermissionState();
-        }
-        @Override
-        public void writeLegacyPermissionStateTEMP() {
-            PermissionManagerService.this.writeLegacyPermissionState();
-        }
-        @Override
-        public void onUserRemoved(@UserIdInt int userId) {
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            PermissionManagerService.this.onUserRemoved(userId);
-        }
-        @NonNull
-        @Override
-        public Set<String> getGrantedPermissions(@NonNull String packageName,
-                @UserIdInt int userId) {
-            Objects.requireNonNull(packageName, "packageName");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            return getGrantedPermissionsInternal(packageName, userId);
-        }
-        @NonNull
-        @Override
-        public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
-            Objects.requireNonNull(permissionName, "permissionName");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            return getPermissionGidsInternal(permissionName, userId);
-        }
-        @NonNull
-        @Override
-        public String[] getAppOpPermissionPackages(@NonNull String permissionName) {
-            Objects.requireNonNull(permissionName, "permissionName");
-            return PermissionManagerService.this.getAppOpPermissionPackagesInternal(permissionName);
-        }
-        @Override
-        public void onStorageVolumeMounted(@Nullable String volumeUuid, boolean fingerprintChanged) {
-            updateAllPermissions(volumeUuid, fingerprintChanged);
-        }
-        @Override
-        public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
-            Objects.requireNonNull(pkg, "pkg");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            resetRuntimePermissionsInternal(pkg, userId);
-        }
-
-        @Override
-        public Permission getPermissionTEMP(String permName) {
-            synchronized (mLock) {
-                return mRegistry.getPermission(permName);
-            }
-        }
-
-        @Override
-        public @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtection(
-                @PermissionInfo.Protection int protection) {
-            ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
-
-            synchronized (mLock) {
-                for (final Permission permission : mRegistry.getPermissions()) {
-                    if (permission.getProtection() == protection) {
-                        matchingPermissions.add(permission.generatePermissionInfo(0));
-                    }
-                }
-            }
-
-            return matchingPermissions;
-        }
-
-        @Override
-        public @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
-                @PermissionInfo.ProtectionFlags int protectionFlags) {
-            ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
-
-            synchronized (mLock) {
-                for (final Permission permission : mRegistry.getPermissions()) {
-                    if ((permission.getProtectionFlags() & protectionFlags) == protectionFlags) {
-                        matchingPermissions.add(permission.generatePermissionInfo(0));
-                    }
-                }
-            }
-
-            return matchingPermissions;
-        }
-
-        @Nullable
-        @Override
-        public byte[] backupRuntimePermissions(@UserIdInt int userId) {
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            return PermissionManagerService.this.backupRuntimePermissions(userId);
-        }
-
-        @Override
-        public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) {
-            Objects.requireNonNull(backup, "backup");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            PermissionManagerService.this.restoreRuntimePermissions(backup, userId);
-        }
-
-        @Override
-        public void restoreDelayedRuntimePermissions(@NonNull String packageName,
-                @UserIdInt int userId) {
-            Objects.requireNonNull(packageName, "packageName");
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            PermissionManagerService.this.restoreDelayedRuntimePermissions(packageName, userId);
-        }
-
-        @Override
-        public void addOnRuntimePermissionStateChangedListener(
-                OnRuntimePermissionStateChangedListener listener) {
-            PermissionManagerService.this.addOnRuntimePermissionStateChangedListener(
-                    listener);
-        }
-
-        @Override
-        public void removeOnRuntimePermissionStateChangedListener(
-                OnRuntimePermissionStateChangedListener listener) {
-            PermissionManagerService.this.removeOnRuntimePermissionStateChangedListener(
-                    listener);
-        }
-
-        @Override
         public void startShellPermissionIdentityDelegation(int uid, @NonNull String packageName,
                 @Nullable List<String> permissionNames) {
             Objects.requireNonNull(packageName, "packageName");
@@ -5288,87 +601,6 @@
         }
 
         @Override
-        public void onUserCreated(@UserIdInt int userId) {
-            Preconditions.checkArgumentNonNegative(userId, "userId");
-            // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
-            updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, true);
-        }
-
-        @Override
-        public void readLegacyPermissionsTEMP(
-                @NonNull LegacyPermissionSettings legacyPermissionSettings) {
-            PermissionManagerService.this.readLegacyPermissions(legacyPermissionSettings);
-        }
-
-        @Override
-        public void writeLegacyPermissionsTEMP(
-                @NonNull LegacyPermissionSettings legacyPermissionSettings) {
-            PermissionManagerService.this.writeLegacyPermissions(legacyPermissionSettings);
-        }
-
-        @Override
-        public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
-                @Nullable AndroidPackage oldPkg) {
-            Objects.requireNonNull(pkg);
-            onPackageAddedInternal(pkg, isInstantApp, oldPkg);
-        }
-
-        @Override
-        public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
-                @NonNull PackageInstalledParams params, @UserIdInt int userId) {
-            Objects.requireNonNull(pkg, "pkg");
-            Objects.requireNonNull(params, "params");
-            Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
-                    || userId == UserHandle.USER_ALL, "userId");
-            final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
-                    : new int[] { userId };
-            onPackageInstalledInternal(pkg, previousAppId, params, userIds);
-        }
-
-        @Override
-        public void onPackageRemoved(@NonNull AndroidPackage pkg) {
-            Objects.requireNonNull(pkg);
-            onPackageRemovedInternal(pkg);
-        }
-
-        @Override
-        public void onPackageUninstalled(@NonNull String packageName, int appId,
-                @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
-                @UserIdInt int userId) {
-            Objects.requireNonNull(packageName, "packageName");
-            Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs");
-            Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
-                    || userId == UserHandle.USER_ALL, "userId");
-            final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
-                    : new int[] { userId };
-            onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds);
-        }
-
-        @NonNull
-        @Override
-        public List<LegacyPermission> getLegacyPermissions() {
-            return PermissionManagerService.this.getLegacyPermissions();
-        }
-
-        @NonNull
-        @Override
-        public Map<String, Set<String>> getAllAppOpPermissionPackages() {
-            return PermissionManagerService.this.getAllAppOpPermissionPackages();
-        }
-
-        @NonNull
-        @Override
-        public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
-            return PermissionManagerService.this.getLegacyPermissionState(appId);
-        }
-
-        @NonNull
-        @Override
-        public int[] getGidsForUid(int uid) {
-            return PermissionManagerService.this.getGidsForUid(uid);
-        }
-
-        @Override
         public void setHotwordDetectionServiceProvider(HotwordDetectionServiceProvider provider) {
             mHotwordDetectionServiceProvider = provider;
         }
@@ -5377,84 +609,174 @@
         public HotwordDetectionServiceProvider getHotwordDetectionServiceProvider() {
             return mHotwordDetectionServiceProvider;
         }
-    }
 
-    /**
-     * Callbacks invoked when interesting actions have been taken on a permission.
-     * <p>
-     * NOTE: The current arguments are merely to support the existing use cases. This
-     * needs to be properly thought out with appropriate arguments for each of the
-     * callback methods.
-     */
-    private static class PermissionCallback {
-        public void onGidsChanged(@AppIdInt int appId, @UserIdInt int userId) {}
-        public void onPermissionChanged() {}
-        public void onPermissionGranted(int uid, @UserIdInt int userId) {}
-        public void onInstallPermissionGranted() {}
-        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {}
-        public void onInstallPermissionRevoked() {}
-        public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {}
-        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
-                int uid) {
-            onPermissionUpdated(updatedUserIds, sync);
+        /* Start of delegate methods to PermissionManagerServiceInterface */
+
+        @NonNull
+        @Override
+        public int[] getGidsForUid(int uid) {
+            return mPermissionManagerServiceImpl.getGidsForUid(uid);
         }
-        public void onPermissionRemoved() {}
-        public void onInstallPermissionUpdated() {}
-        public void onInstallPermissionUpdatedNotifyListener(int uid) {
-            onInstallPermissionUpdated();
-        }
-    }
 
-    private static final class OnPermissionChangeListeners extends Handler {
-        private static final int MSG_ON_PERMISSIONS_CHANGED = 1;
-
-        private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners =
-                new RemoteCallbackList<>();
-
-        OnPermissionChangeListeners(Looper looper) {
-            super(looper);
+        @NonNull
+        @Override
+        public Map<String, Set<String>> getAllAppOpPermissionPackages() {
+            return mPermissionManagerServiceImpl.getAllAppOpPermissionPackages();
         }
 
         @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ON_PERMISSIONS_CHANGED: {
-                    final int uid = msg.arg1;
-                    handleOnPermissionsChanged(uid);
-                } break;
-            }
+        public void onUserCreated(@UserIdInt int userId) {
+            mPermissionManagerServiceImpl.onUserCreated(userId);
         }
 
-        public void addListener(IOnPermissionsChangeListener listener) {
-            mPermissionListeners.register(listener);
+        @NonNull
+        @Override
+        public List<LegacyPermission> getLegacyPermissions() {
+            return mPermissionManagerServiceImpl.getLegacyPermissions();
         }
 
-        public void removeListener(IOnPermissionsChangeListener listener) {
-            mPermissionListeners.unregister(listener);
+        @NonNull
+        @Override
+        public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
+            return mPermissionManagerServiceImpl.getLegacyPermissionState(appId);
         }
 
-        public void onPermissionsChanged(int uid) {
-            if (mPermissionListeners.getRegisteredCallbackCount() > 0) {
-                obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
-            }
+        @Nullable
+        @Override
+        public byte[] backupRuntimePermissions(@UserIdInt int userId) {
+            return mPermissionManagerServiceImpl.backupRuntimePermissions(userId);
         }
 
-        private void handleOnPermissionsChanged(int uid) {
-            final int count = mPermissionListeners.beginBroadcast();
-            try {
-                for (int i = 0; i < count; i++) {
-                    IOnPermissionsChangeListener callback = mPermissionListeners
-                            .getBroadcastItem(i);
-                    try {
-                        callback.onPermissionsChanged(uid);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Permission listener is dead", e);
-                    }
-                }
-            } finally {
-                mPermissionListeners.finishBroadcast();
-            }
+        @Override
+        public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.restoreRuntimePermissions(backup, userId);
         }
+
+        @Override
+        public void restoreDelayedRuntimePermissions(@NonNull String packageName,
+                @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.restoreDelayedRuntimePermissions(packageName, userId);
+        }
+
+        @Override
+        public void readLegacyPermissionsTEMP(
+                @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+            mPermissionManagerServiceImpl.readLegacyPermissionsTEMP(legacyPermissionSettings);
+        }
+
+        @Override
+        public void writeLegacyPermissionsTEMP(
+                @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+            mPermissionManagerServiceImpl.writeLegacyPermissionsTEMP(legacyPermissionSettings);
+        }
+
+        @Override
+        public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+                @Nullable AndroidPackage oldPkg) {
+            mPermissionManagerServiceImpl.onPackageAdded(pkg, isInstantApp, oldPkg);
+        }
+
+        @Override
+        public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
+                @NonNull PackageInstalledParams params, @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.onPackageInstalled(pkg, previousAppId, params, userId);
+        }
+
+        @Override
+        public void onPackageRemoved(@NonNull AndroidPackage pkg) {
+            mPermissionManagerServiceImpl.onPackageRemoved(pkg);
+        }
+
+        @Override
+        public void onPackageUninstalled(@NonNull String packageName, int appId,
+                @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
+                @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.onPackageUninstalled(packageName, appId,
+                    pkg, sharedUserPkgs, userId);
+        }
+
+        @Override
+        public void addOnRuntimePermissionStateChangedListener(
+                OnRuntimePermissionStateChangedListener listener) {
+            mPermissionManagerServiceImpl.addOnRuntimePermissionStateChangedListener(listener);
+        }
+
+        @Override
+        public void removeOnRuntimePermissionStateChangedListener(
+                OnRuntimePermissionStateChangedListener listener) {
+            mPermissionManagerServiceImpl.removeOnRuntimePermissionStateChangedListener(listener);
+        }
+
+        @Override
+        public void onSystemReady() {
+            mPermissionManagerServiceImpl.onSystemReady();
+        }
+
+        @Override
+        public boolean isPermissionsReviewRequired(@NonNull String packageName,
+                @UserIdInt int userId) {
+            return mPermissionManagerServiceImpl.isPermissionsReviewRequired(packageName, userId);
+        }
+
+        @Override
+        public void readLegacyPermissionStateTEMP() {
+            mPermissionManagerServiceImpl.readLegacyPermissionStateTEMP();
+        }
+        @Override
+        public void writeLegacyPermissionStateTEMP() {
+            mPermissionManagerServiceImpl.writeLegacyPermissionStateTEMP();
+        }
+        @Override
+        public void onUserRemoved(@UserIdInt int userId) {
+            mPermissionManagerServiceImpl.onUserRemoved(userId);
+        }
+        @NonNull
+        @Override
+        public Set<String> getGrantedPermissions(@NonNull String packageName,
+                @UserIdInt int userId) {
+            return mPermissionManagerServiceImpl.getGrantedPermissions(packageName, userId);
+        }
+        @NonNull
+        @Override
+        public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
+            return mPermissionManagerServiceImpl.getPermissionGids(permissionName, userId);
+        }
+        @NonNull
+        @Override
+        public String[] getAppOpPermissionPackages(@NonNull String permissionName) {
+            return mPermissionManagerServiceImpl.getAppOpPermissionPackages(permissionName);
+        }
+        @Override
+        public void onStorageVolumeMounted(@Nullable String volumeUuid,
+                boolean fingerprintChanged) {
+            mPermissionManagerServiceImpl.onStorageVolumeMounted(volumeUuid, fingerprintChanged);
+        }
+        @Override
+        public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.resetRuntimePermissions(pkg, userId);
+        }
+
+        @Override
+        public Permission getPermissionTEMP(String permName) {
+            return mPermissionManagerServiceImpl.getPermissionTEMP(permName);
+        }
+
+        @NonNull
+        @Override
+        public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+                @PermissionInfo.Protection int protection) {
+            return mPermissionManagerServiceImpl.getAllPermissionsWithProtection(protection);
+        }
+
+        @NonNull
+        @Override
+        public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+                @PermissionInfo.ProtectionFlags int protectionFlags) {
+            return mPermissionManagerServiceImpl
+                    .getAllPermissionsWithProtectionFlags(protectionFlags);
+        }
+
+        /* End of delegate methods to PermissionManagerServiceInterface */
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
new file mode 100644
index 0000000..d1b9938
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -0,0 +1,5060 @@
+/*
+ * 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.permission;
+
+import static android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE;
+import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL;
+import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static android.permission.PermissionManager.KILL_APP_REASON_GIDS_CHANGED;
+import static android.permission.PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED;
+
+import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE;
+import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
+import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
+import static com.android.server.pm.PackageManagerService.DEBUG_PERMISSIONS;
+import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.Manifest;
+import android.annotation.AppIdInt;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.SigningDetails;
+import android.content.pm.parsing.component.ComponentMutateUtils;
+import android.content.pm.parsing.component.ParsedPermission;
+import android.content.pm.parsing.component.ParsedPermissionGroup;
+import android.content.pm.parsing.component.ParsedPermissionUtils;
+import android.content.pm.permission.CompatibilityPermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.metrics.LogMaker;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionControllerManager;
+import android.permission.PermissionManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.DebugUtils;
+import android.util.EventLog;
+import android.util.IntArray;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.os.RoSystemProperties;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IntPair;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.ApexManager;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerService;
+import com.android.server.pm.parsing.PackageInfoUtils;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.policy.PermissionPolicyInternal;
+import com.android.server.policy.SoftRestrictedPermissionPolicy;
+
+import libcore.util.EmptyArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * PermissionManagerServiceImpl.
+ */
+public class PermissionManagerServiceImpl implements PermissionManagerServiceInterface {
+
+    private static final String TAG = "PackageManager";
+    private static final String LOG_TAG = PermissionManagerServiceImpl.class.getSimpleName();
+
+    private static final long BACKUP_TIMEOUT_MILLIS = SECONDS.toMillis(60);
+
+    // For automotive products, CarService enforces allow-listing of the privileged permissions
+    // com.android.car is the package name which declares auto specific permissions
+    private static final String CAR_PACKAGE_NAME = "com.android.car";
+
+    /** Cap the size of permission trees that 3rd party apps can define; in characters of text */
+    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;
+    /** Empty array to avoid allocations */
+    private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+    /**
+     * When these flags are set, the system should not automatically modify the permission grant
+     * state.
+     */
+    private static final int BLOCKING_PERMISSION_FLAGS = FLAG_PERMISSION_SYSTEM_FIXED
+            | FLAG_PERMISSION_POLICY_FIXED
+            | FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+
+    /** Permission flags set by the user */
+    private static final int USER_PERMISSION_FLAGS = FLAG_PERMISSION_USER_SET
+            | FLAG_PERMISSION_USER_FIXED;
+
+    /** All storage permissions */
+    private static final List<String> STORAGE_PERMISSIONS = new ArrayList<>();
+    /** All nearby devices permissions */
+    private static final List<String> NEARBY_DEVICES_PERMISSIONS = new ArrayList<>();
+
+    /**
+     * All permissions that should be granted with the REVOKE_WHEN_REQUESTED flag, if they are
+     * implicitly added to a package
+     */
+    private static final List<String> IMPLICIT_GRANTED_PERMISSIONS = new ArrayList<>();
+
+    /** If the permission of the value is granted, so is the key */
+    private static final Map<String, String> FULLER_PERMISSION_MAP = new HashMap<>();
+
+    static {
+        FULLER_PERMISSION_MAP.put(Manifest.permission.ACCESS_COARSE_LOCATION,
+                Manifest.permission.ACCESS_FINE_LOCATION);
+        FULLER_PERMISSION_MAP.put(Manifest.permission.INTERACT_ACROSS_USERS,
+                Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
+        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_ADVERTISE);
+        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_CONNECT);
+        NEARBY_DEVICES_PERMISSIONS.add(Manifest.permission.BLUETOOTH_SCAN);
+        IMPLICIT_GRANTED_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
+    }
+
+    /** Set of source package names for Privileged Permission Allowlist */
+    private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames =
+            new ArraySet<>();
+
+    /** Lock to protect internal data access */
+    private final Object mLock = new Object();
+
+    /** Internal connection to the package manager */
+    private final PackageManagerInternal mPackageManagerInt;
+
+    /** Internal connection to the user manager */
+    private final UserManagerInternal mUserManagerInt;
+
+    @GuardedBy("mLock")
+    @NonNull
+    private final DevicePermissionState mState = new DevicePermissionState();
+
+    /** Permission controller: User space permission management */
+    private PermissionControllerManager mPermissionControllerManager;
+
+    /** App ops manager */
+    private final AppOpsManager mAppOpsManager;
+
+    /**
+     * Built-in permissions. Read from system configuration files. Mapping is from
+     * UID to permission name.
+     */
+    private final SparseArray<ArraySet<String>> mSystemPermissions;
+
+    /** Built-in group IDs given to all packages. Read from system configuration files. */
+    @NonNull
+    private final int[] mGlobalGids;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
+    private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+            ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+    /** Internal storage for permissions and related settings */
+    @GuardedBy("mLock")
+    @NonNull
+    private final PermissionRegistry mRegistry = new PermissionRegistry();
+
+    @GuardedBy("mLock")
+    @Nullable
+    private ArraySet<String> mPrivappPermissionsViolations;
+
+    @GuardedBy("mLock")
+    private boolean mSystemReady;
+
+    @GuardedBy("mLock")
+    private PermissionPolicyInternal mPermissionPolicyInternal;
+
+    /**
+     * A permission backup might contain apps that are not installed. In this case we delay the
+     * restoration until the app is installed.
+     *
+     * <p>This array ({@code userId -> noDelayedBackupLeft}) is {@code true} for all the users where
+     * there is <u>no more</u> delayed backup left.
+     */
+    @GuardedBy("mLock")
+    private final SparseBooleanArray mHasNoDelayedPermBackup = new SparseBooleanArray();
+
+    /** Listeners for permission state (granting and flags) changes */
+    @GuardedBy("mLock")
+    private final ArrayList<PermissionManagerServiceInternal
+            .OnRuntimePermissionStateChangedListener>
+            mRuntimePermissionStateChangedListeners = new ArrayList<>();
+
+    @NonNull
+    private final OnPermissionChangeListeners mOnPermissionChangeListeners;
+
+    // TODO: Take a look at the methods defined in the callback.
+    // The callback was initially created to support the split between permission
+    // manager and the package manager. However, it's started to be used for other
+    // purposes. It may make sense to keep as an abstraction, but, the methods
+    // necessary to be overridden may be different than what was initially needed
+    // for the split.
+    private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() {
+        @Override
+        public void onGidsChanged(int appId, int userId) {
+            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
+        }
+        @Override
+        public void onPermissionGranted(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Not critical; if this is lost, the application has to request again.
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onInstallPermissionGranted() {
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onPermissionRevoked(int uid, int userId, String reason) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Critical; after this call the application should never have the permission
+            mPackageManagerInt.writeSettings(false);
+            final int appId = UserHandle.getAppId(uid);
+            if (reason == null) {
+                mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
+            } else {
+                mHandler.post(() -> killUid(appId, userId, reason));
+            }
+        }
+        @Override
+        public void onInstallPermissionRevoked() {
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onPermissionUpdated(int[] userIds, boolean sync) {
+            mPackageManagerInt.writePermissionSettings(userIds, !sync);
+        }
+        @Override
+        public void onInstallPermissionUpdated() {
+            mPackageManagerInt.writeSettings(true);
+        }
+        @Override
+        public void onPermissionRemoved() {
+            mPackageManagerInt.writeSettings(false);
+        }
+        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
+                int uid) {
+            onPermissionUpdated(updatedUserIds, sync);
+            for (int i = 0; i < updatedUserIds.length; i++) {
+                int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
+                mOnPermissionChangeListeners.onPermissionsChanged(userUid);
+            }
+        }
+        public void onInstallPermissionUpdatedNotifyListener(int uid) {
+            onInstallPermissionUpdated();
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+        }
+    };
+
+    public PermissionManagerServiceImpl(@NonNull Context context,
+            @NonNull ArrayMap<String, FeatureInfo> availableFeatures) {
+        // The package info cache is the cache for package and permission information.
+        // Disable the package info and package permission caches locally but leave the
+        // checkPermission cache active.
+        PackageManager.invalidatePackageInfoCache();
+        PermissionManager.disablePackageNamePermissionCache();
+
+        mContext = context;
+        mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
+        mAppOpsManager = context.getSystemService(AppOpsManager.class);
+
+        mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME);
+        // PackageManager.hasSystemFeature() is not used here because PackageManagerService
+        // isn't ready yet.
+        if (availableFeatures.containsKey(PackageManager.FEATURE_AUTOMOTIVE)) {
+            mPrivilegedPermissionAllowlistSourcePackageNames.add(CAR_PACKAGE_NAME);
+        }
+
+        mHandlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        Watchdog.getInstance().addThread(mHandler);
+
+        SystemConfig systemConfig = SystemConfig.getInstance();
+        mSystemPermissions = systemConfig.getSystemPermissions();
+        mGlobalGids = systemConfig.getGlobalGids();
+        mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper());
+
+        // propagate permission configuration
+        final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
+                SystemConfig.getInstance().getPermissions();
+        synchronized (mLock) {
+            for (int i = 0; i < permConfig.size(); i++) {
+                final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
+                Permission bp = mRegistry.getPermission(perm.name);
+                if (bp == null) {
+                    bp = new Permission(perm.name, "android", Permission.TYPE_CONFIG);
+                    mRegistry.addPermission(bp);
+                }
+                if (perm.gids != null) {
+                    bp.setGids(perm.gids, perm.perUser);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+            return;
+        }
+
+        mContext.getSystemService(PermissionControllerManager.class).dump(fd, args);
+    }
+
+    /**
+     * This method should typically only be used when granting or revoking
+     * permissions, since the app may immediately restart after this call.
+     * <p>
+     * If you're doing surgery on app code/data, use {@link PackageFreezer} to
+     * guard your work against the app being relaunched.
+     */
+    private static void killUid(int appId, int userId, String reason) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            IActivityManager am = ActivityManager.getService();
+            if (am != null) {
+                try {
+                    am.killUidForPermissionChange(appId, userId, reason);
+                } catch (RemoteException e) {
+                    /* ignore - same process */
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @NonNull
+    private String[] getAppOpPermissionPackagesInternal(@NonNull String permissionName) {
+        synchronized (mLock) {
+            final ArraySet<String> packageNames = mRegistry.getAppOpPermissionPackages(
+                    permissionName);
+            if (packageNames == null) {
+                return EmptyArray.STRING;
+            }
+            return packageNames.toArray(new String[0]);
+        }
+    }
+
+    @Override
+    @NonNull
+    public List<PermissionGroupInfo> getAllPermissionGroups(
+            @PackageManager.PermissionGroupInfoFlags int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return Collections.emptyList();
+        }
+
+        final List<PermissionGroupInfo> out = new ArrayList<>();
+        synchronized (mLock) {
+            for (ParsedPermissionGroup pg : mRegistry.getPermissionGroups()) {
+                out.add(PackageInfoUtils.generatePermissionGroupInfo(pg, flags));
+            }
+        }
+
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid,
+                callingUserId));
+        return out;
+    }
+
+    @Override
+    @Nullable
+    public PermissionGroupInfo getPermissionGroupInfo(String groupName,
+            @PackageManager.PermissionGroupInfoFlags int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+
+        final PermissionGroupInfo permissionGroupInfo;
+        synchronized (mLock) {
+            final ParsedPermissionGroup permissionGroup = mRegistry.getPermissionGroup(groupName);
+            if (permissionGroup == null) {
+                return null;
+            }
+            permissionGroupInfo = PackageInfoUtils.generatePermissionGroupInfo(permissionGroup,
+                    flags);
+        }
+
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (mPackageManagerInt.filterAppAccess(permissionGroupInfo.packageName, callingUid,
+                callingUserId)) {
+            EventLog.writeEvent(0x534e4554, "186113473", callingUid, groupName);
+            return null;
+        }
+        return permissionGroupInfo;
+    }
+
+    @Override
+    @Nullable
+    public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
+            @PackageManager.PermissionInfoFlags int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+
+        final AndroidPackage opPackage = mPackageManagerInt.getPackage(opPackageName);
+        final int targetSdkVersion = getPermissionInfoCallingTargetSdkVersion(opPackage,
+                callingUid);
+        final PermissionInfo permissionInfo;
+        synchronized (mLock) {
+            final Permission bp = mRegistry.getPermission(permName);
+            if (bp == null) {
+                return null;
+            }
+            permissionInfo = bp.generatePermissionInfo(flags, targetSdkVersion);
+        }
+
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (mPackageManagerInt.filterAppAccess(permissionInfo.packageName, callingUid,
+                callingUserId)) {
+            EventLog.writeEvent(0x534e4554, "183122164", callingUid, permName);
+            return null;
+        }
+        return permissionInfo;
+    }
+
+    private int getPermissionInfoCallingTargetSdkVersion(@Nullable AndroidPackage pkg, int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID
+                || appId == Process.SHELL_UID) {
+            // System sees all flags.
+            return Build.VERSION_CODES.CUR_DEVELOPMENT;
+        }
+        if (pkg == null) {
+            return Build.VERSION_CODES.CUR_DEVELOPMENT;
+        }
+        return pkg.getTargetSdkVersion();
+    }
+
+    @Override
+    @Nullable
+    public List<PermissionInfo> queryPermissionsByGroup(String groupName,
+            @PackageManager.PermissionInfoFlags int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+
+        final List<PermissionInfo> out = new ArrayList<>(10);
+        synchronized (mLock) {
+            if (groupName != null && mRegistry.getPermissionGroup(groupName) == null) {
+                return null;
+            }
+            for (Permission bp : mRegistry.getPermissions()) {
+                if (Objects.equals(bp.getGroup(), groupName)) {
+                    out.add(bp.generatePermissionInfo(flags));
+                }
+            }
+        }
+
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        out.removeIf(it -> mPackageManagerInt.filterAppAccess(it.packageName, callingUid,
+                callingUserId));
+        return out;
+    }
+
+    @Override
+    public boolean addPermission(PermissionInfo info, boolean async) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant apps can't add permissions");
+        }
+        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
+            throw new SecurityException("Label must be specified in permission");
+        }
+        final boolean added;
+        final boolean changed;
+        synchronized (mLock) {
+            final Permission tree = mRegistry.enforcePermissionTree(info.name, callingUid);
+            Permission bp = mRegistry.getPermission(info.name);
+            added = bp == null;
+            int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+            if (added) {
+                enforcePermissionCapLocked(info, tree);
+                bp = new Permission(info.name, tree.getPackageName(), Permission.TYPE_DYNAMIC);
+            } else if (!bp.isDynamic()) {
+                throw new SecurityException("Not allowed to modify non-dynamic permission "
+                        + info.name);
+            }
+            changed = bp.addToTree(fixedLevel, info, tree);
+            if (added) {
+                mRegistry.addPermission(bp);
+            }
+        }
+        if (changed) {
+            mPackageManagerInt.writeSettings(async);
+        }
+        return added;
+    }
+
+    @Override
+    public void removePermission(String permName) {
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant applications don't have access to this method");
+        }
+        synchronized (mLock) {
+            mRegistry.enforcePermissionTree(permName, callingUid);
+            final Permission bp = mRegistry.getPermission(permName);
+            if (bp == null) {
+                return;
+            }
+            if (bp.isDynamic()) {
+                // TODO: switch this back to SecurityException
+                Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
+                        + permName);
+            }
+            mRegistry.removePermission(permName);
+        }
+        mPackageManagerInt.writeSettings(false);
+    }
+
+    @Override
+    public int getPermissionFlags(String packageName, String permName, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        return getPermissionFlagsInternal(packageName, permName, callingUid, userId);
+    }
+
+    private int getPermissionFlagsInternal(
+            String packageName, String permName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return 0;
+        }
+
+        enforceGrantRevokeGetRuntimePermissionPermissions("getPermissionFlags");
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                false, // checkShell
+                "getPermissionFlags");
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return 0;
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            return 0;
+        }
+
+        synchronized (mLock) {
+            if (mRegistry.getPermission(permName) == null) {
+                return 0;
+            }
+
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
+                return 0;
+            }
+
+            return uidState.getPermissionFlags(permName);
+        }
+    }
+
+    @Override
+    public void updatePermissionFlags(String packageName, String permName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        boolean overridePolicy = false;
+
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0) {
+                    if (checkAdjustPolicyFlagPermission) {
+                        mContext.enforceCallingOrSelfPermission(
+                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
+                                "Need " + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY
+                                        + " to change policy flags");
+                    } else if (mPackageManagerInt.getUidTargetSdkVersion(callingUid)
+                            >= Build.VERSION_CODES.Q) {
+                        throw new IllegalArgumentException(
+                                Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY + " needs "
+                                        + " to be checked for packages targeting "
+                                        + Build.VERSION_CODES.Q + " or later when changing policy "
+                                        + "flags");
+                    }
+                    overridePolicy = true;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        updatePermissionFlagsInternal(
+                packageName, permName, flagMask, flagValues, callingUid, userId,
+                overridePolicy, mDefaultPermissionCallback);
+    }
+
+    private void updatePermissionFlagsInternal(String packageName, String permName, int flagMask,
+            int flagValues, int callingUid, int userId, boolean overridePolicy,
+            PermissionCallback callback) {
+        if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
+                && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
+            Log.i(TAG, "System is updating flags for " + packageName + " "
+                            + permName + " for user " + userId  + " "
+                            + DebugUtils.flagsToString(
+                                    PackageManager.class, "FLAG_PERMISSION_", flagMask)
+                            + " := "
+                            + DebugUtils.flagsToString(
+                                    PackageManager.class, "FLAG_PERMISSION_", flagValues)
+                            + " on behalf of uid " + callingUid
+                            + " " + mPackageManagerInt.getNameForUid(callingUid),
+                    new RuntimeException());
+        }
+
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                "updatePermissionFlags");
+
+        if ((flagMask & FLAG_PERMISSION_POLICY_FIXED) != 0 && !overridePolicy) {
+            throw new SecurityException("updatePermissionFlags requires "
+                    + Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
+        }
+
+        // Only the system can change these flags and nothing else.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+            flagValues &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
+        }
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            Log.e(TAG, "Unknown package: " + packageName);
+            return;
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        boolean isRequested = false;
+        // Fast path, the current package has requested the permission.
+        if (pkg.getRequestedPermissions().contains(permName)) {
+            isRequested = true;
+        }
+        if (!isRequested) {
+            // Slow path, go through all shared user packages.
+            String[] sharedUserPackageNames =
+                    mPackageManagerInt.getSharedUserPackagesForPackage(packageName, userId);
+            for (String sharedUserPackageName : sharedUserPackageNames) {
+                AndroidPackage sharedUserPkg = mPackageManagerInt.getPackage(
+                        sharedUserPackageName);
+                if (sharedUserPkg != null
+                        && sharedUserPkg.getRequestedPermissions().contains(permName)) {
+                    isRequested = true;
+                    break;
+                }
+            }
+        }
+
+        final boolean isRuntimePermission;
+        final boolean permissionUpdated;
+        synchronized (mLock) {
+            final Permission bp = mRegistry.getPermission(permName);
+            if (bp == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permName);
+            }
+
+            isRuntimePermission = bp.isRuntime();
+
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
+                return;
+            }
+
+            if (!uidState.hasPermissionState(permName) && !isRequested) {
+                Log.e(TAG, "Permission " + permName + " isn't requested by package " + packageName);
+                return;
+            }
+
+            permissionUpdated = uidState.updatePermissionFlags(bp, flagMask, flagValues);
+        }
+
+        if (permissionUpdated && isRuntimePermission) {
+            notifyRuntimePermissionStateChanged(packageName, userId);
+        }
+        if (permissionUpdated && callback != null) {
+            // Install and runtime permissions are stored in different places,
+            // so figure out what permission changed and persist the change.
+            if (!isRuntimePermission) {
+                int userUid = UserHandle.getUid(userId, pkg.getUid());
+                callback.onInstallPermissionUpdatedNotifyListener(userUid);
+            } else {
+                callback.onPermissionUpdatedNotifyListener(new int[]{userId}, false, pkg.getUid());
+            }
+        }
+    }
+
+    /**
+     * Update the permission flags for all packages and runtime permissions of a user in order
+     * to allow device or profile owner to remove POLICY_FIXED.
+     */
+    @Override
+    public void updatePermissionFlagsForAllApps(int flagMask, int flagValues,
+            final int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions(
+                "updatePermissionFlagsForAllApps");
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                "updatePermissionFlagsForAllApps");
+
+        // Only the system can change system fixed flags.
+        final int effectiveFlagMask = (callingUid != Process.SYSTEM_UID)
+                ? flagMask : flagMask & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+        final int effectiveFlagValues = (callingUid != Process.SYSTEM_UID)
+                ? flagValues : flagValues & ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+
+        final boolean[] changed = new boolean[1];
+        mPackageManagerInt.forEachPackage(pkg -> {
+            synchronized (mLock) {
+                final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+                if (uidState == null) {
+                    Slog.e(TAG,
+                            "Missing permissions state for " + pkg.getPackageName() + " and user "
+                                    + userId);
+                    return;
+                }
+                changed[0] |= uidState.updatePermissionFlagsForAllPermissions(
+                        effectiveFlagMask, effectiveFlagValues);
+            }
+            mOnPermissionChangeListeners.onPermissionsChanged(pkg.getUid());
+        });
+
+        if (changed[0]) {
+            mPackageManagerInt.writePermissionSettings(new int[] { userId }, true);
+        }
+    }
+
+    @Override
+    public int checkPermission(String pkgName, String permName, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(pkgName);
+        if (pkg == null) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+        return checkPermissionInternal(pkg, true, permName, userId);
+    }
+
+    private int checkPermissionInternal(@NonNull AndroidPackage pkg, boolean isPackageExplicit,
+            @NonNull String permissionName, @UserIdInt int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (isPackageExplicit || pkg.getSharedUserId() == null) {
+            if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+        } else {
+            if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+        }
+
+        final int uid = UserHandle.getUid(userId, pkg.getUid());
+        final boolean isInstantApp = mPackageManagerInt.getInstantAppPackageName(uid) != null;
+
+        synchronized (mLock) {
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
+                return PackageManager.PERMISSION_DENIED;
+            }
+
+            if (checkSinglePermissionInternalLocked(uidState, permissionName, isInstantApp)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+
+            final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
+            if (fullerPermissionName != null && checkSinglePermissionInternalLocked(uidState,
+                    fullerPermissionName, isInstantApp)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    @GuardedBy("mLock")
+    private boolean checkSinglePermissionInternalLocked(@NonNull UidPermissionState uidState,
+            @NonNull String permissionName, boolean isInstantApp) {
+        if (!uidState.isPermissionGranted(permissionName)) {
+            return false;
+        }
+
+        if (isInstantApp) {
+            final Permission permission = mRegistry.getPermission(permissionName);
+            return permission != null && permission.isInstant();
+        }
+
+        return true;
+    }
+
+    @Override
+    public int checkUidPermission(int uid, String permName) {
+        final int userId = UserHandle.getUserId(uid);
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
+        return checkUidPermissionInternal(pkg, uid, permName);
+    }
+
+    /**
+     * Checks whether or not the given package has been granted the specified
+     * permission. If the given package is {@code null}, we instead check the
+     * system permissions for the given UID.
+     *
+     * @see SystemConfig#getSystemPermissions()
+     */
+    private int checkUidPermissionInternal(@Nullable AndroidPackage pkg, int uid,
+            @NonNull String permissionName) {
+        if (pkg != null) {
+            final int userId = UserHandle.getUserId(uid);
+            return checkPermissionInternal(pkg, false, permissionName, userId);
+        }
+
+        synchronized (mLock) {
+            if (checkSingleUidPermissionInternalLocked(uid, permissionName)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+
+            final String fullerPermissionName = FULLER_PERMISSION_MAP.get(permissionName);
+            if (fullerPermissionName != null
+                    && checkSingleUidPermissionInternalLocked(uid, fullerPermissionName)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    @GuardedBy("mLock")
+    private boolean checkSingleUidPermissionInternalLocked(int uid,
+            @NonNull String permissionName) {
+        ArraySet<String> permissions = mSystemPermissions.get(uid);
+        return permissions != null && permissions.contains(permissionName);
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS,
+                "addOnPermissionsChangeListener");
+
+        mOnPermissionChangeListeners.addListener(listener);
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener) {
+        if (mPackageManagerInt.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+            throw new SecurityException("Instant applications don't have access to this method");
+        }
+        mOnPermissionChangeListeners.removeListener(listener);
+    }
+
+    @Nullable
+    @Override
+    public List<String> getAllowlistedRestrictedPermissions(@NonNull String packageName,
+            @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) {
+        Objects.requireNonNull(packageName);
+        Preconditions.checkFlagsArgument(flags,
+                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+        Preconditions.checkArgumentNonNegative(userId, null);
+
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS,
+                    "getAllowlistedRestrictedPermissions for user " + userId);
+        }
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return null;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) {
+            return null;
+        }
+        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
+                        == PackageManager.PERMISSION_GRANTED;
+        final boolean isCallerInstallerOnRecord =
+                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
+
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0
+                && !isCallerPrivileged) {
+            throw new SecurityException("Querying system allowlist requires "
+                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+        }
+
+        if ((flags & (PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+                | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) != 0) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw new SecurityException("Querying upgrade or installer allowlist"
+                        + " requires being installer on record or "
+                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+            }
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            return getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Nullable
+    private List<String> getAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
+            @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) {
+        synchronized (mLock) {
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
+                return null;
+            }
+
+            int queryFlags = 0;
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0) {
+                queryFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+            }
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
+                queryFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+            }
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+                queryFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+            }
+
+            ArrayList<String> allowlistedPermissions = null;
+
+            final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
+            for (int i = 0; i < permissionCount; i++) {
+                final String permissionName = pkg.getRequestedPermissions().get(i);
+                final int currentFlags =
+                        uidState.getPermissionFlags(permissionName);
+                if ((currentFlags & queryFlags) != 0) {
+                    if (allowlistedPermissions == null) {
+                        allowlistedPermissions = new ArrayList<>();
+                    }
+                    allowlistedPermissions.add(permissionName);
+                }
+            }
+
+            return allowlistedPermissions;
+        }
+    }
+
+    @Override
+    public boolean addAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags,
+            @UserIdInt int userId) {
+        // Other argument checks are done in get/setAllowlistedRestrictedPermissions
+        Objects.requireNonNull(permName);
+
+        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
+            return false;
+        }
+
+        List<String> permissions =
+                getAllowlistedRestrictedPermissions(packageName, flags, userId);
+        if (permissions == null) {
+            permissions = new ArrayList<>(1);
+        }
+        if (permissions.indexOf(permName) < 0) {
+            permissions.add(permName);
+            return setAllowlistedRestrictedPermissions(packageName, permissions,
+                    flags, userId);
+        }
+        return false;
+    }
+
+    private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
+            @NonNull String permName) {
+        final String permissionPackageName;
+        final boolean isImmutablyRestrictedPermission;
+        synchronized (mLock) {
+            final Permission bp = mRegistry.getPermission(permName);
+            if (bp == null) {
+                Slog.w(TAG, "No such permissions: " + permName);
+                return false;
+            }
+            permissionPackageName = bp.getPackageName();
+            isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted()
+                    && bp.isImmutablyRestricted();
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) {
+            EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName);
+            return false;
+        }
+
+        if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Cannot modify allowlisting of an immutably "
+                    + "restricted permission: " + permName);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean removeAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags,
+            @UserIdInt int userId) {
+        // Other argument checks are done in get/setAllowlistedRestrictedPermissions
+        Objects.requireNonNull(permName);
+
+        if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
+            return false;
+        }
+
+        final List<String> permissions =
+                getAllowlistedRestrictedPermissions(packageName, flags, userId);
+        if (permissions != null && permissions.remove(permName)) {
+            return setAllowlistedRestrictedPermissions(packageName, permissions,
+                    flags, userId);
+        }
+        return false;
+    }
+
+    private boolean setAllowlistedRestrictedPermissions(@NonNull String packageName,
+            @Nullable List<String> permissions, @PackageManager.PermissionWhitelistFlags int flags,
+            @UserIdInt int userId) {
+        Objects.requireNonNull(packageName);
+        Preconditions.checkFlagsArgument(flags,
+                PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+                        | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
+        Preconditions.checkArgument(Integer.bitCount(flags) == 1);
+        Preconditions.checkArgumentNonNegative(userId, null);
+
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    "setAllowlistedRestrictedPermissions for user " + userId);
+        }
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return false;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, UserHandle.getCallingUserId())) {
+            return false;
+        }
+
+        final boolean isCallerPrivileged = mContext.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
+                        == PackageManager.PERMISSION_GRANTED;
+        final boolean isCallerInstallerOnRecord =
+                mPackageManagerInt.isCallerInstallerOfRecord(pkg, callingUid);
+
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) != 0 && !isCallerPrivileged) {
+            throw new SecurityException("Modifying system allowlist requires "
+                    + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+        }
+
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE) != 0) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw new SecurityException("Modifying upgrade allowlist requires"
+                        + " being installer on record or "
+                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+            }
+            final List<String> allowlistedPermissions =
+                    getAllowlistedRestrictedPermissions(pkg.getPackageName(), flags, userId);
+            if (permissions == null || permissions.isEmpty()) {
+                if (allowlistedPermissions == null || allowlistedPermissions.isEmpty()) {
+                    return true;
+                }
+            } else {
+                // Only the system can add and remove while the installer can only remove.
+                final int permissionCount = permissions.size();
+                for (int i = 0; i < permissionCount; i++) {
+                    if ((allowlistedPermissions == null
+                            || !allowlistedPermissions.contains(permissions.get(i)))
+                            && !isCallerPrivileged) {
+                        throw new SecurityException("Adding to upgrade allowlist requires"
+                                + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+                    }
+                }
+            }
+
+            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                    throw new SecurityException("Modifying installer allowlist requires"
+                            + " being installer on record or "
+                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
+                }
+            }
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permName, final int userId) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean overridePolicy =
+                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
+                        == PackageManager.PERMISSION_GRANTED;
+
+        grantRuntimePermissionInternal(packageName, permName, overridePolicy,
+                callingUid, userId, mDefaultPermissionCallback);
+    }
+
+    private void grantRuntimePermissionInternal(String packageName, String permName,
+            boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
+        if (PermissionManager.DEBUG_TRACE_GRANTS
+                && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
+            Log.i(PermissionManager.LOG_TAG_TRACE_GRANTS, "System is granting " + packageName + " "
+                    + permName + " for user " + userId + " on behalf of uid " + callingUid
+                    + " " + mPackageManagerInt.getNameForUid(callingUid),
+                    new RuntimeException());
+        }
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                "grantRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                "grantRuntimePermission");
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName);
+        if (pkg == null || ps == null) {
+            Log.e(TAG, "Unknown package: " + packageName);
+            return;
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        final boolean isRolePermission;
+        final boolean isSoftRestrictedPermission;
+        synchronized (mLock) {
+            final Permission permission = mRegistry.getPermission(permName);
+            if (permission == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permName);
+            }
+            isRolePermission = permission.isRole();
+            isSoftRestrictedPermission = permission.isSoftRestricted();
+        }
+        final boolean mayGrantRolePermission = isRolePermission
+                && mayManageRolePermission(callingUid);
+        final boolean mayGrantSoftRestrictedPermission = isSoftRestrictedPermission
+                && SoftRestrictedPermissionPolicy.forPermission(mContext,
+                        AndroidPackageUtils.generateAppInfoWithoutState(pkg), pkg,
+                        UserHandle.of(userId), permName)
+                        .mayGrantPermission();
+
+        final boolean isRuntimePermission;
+        final boolean permissionHasGids;
+        synchronized (mLock) {
+            final Permission bp = mRegistry.getPermission(permName);
+            if (bp == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permName);
+            }
+
+            isRuntimePermission = bp.isRuntime();
+            permissionHasGids = bp.hasGids();
+            if (isRuntimePermission || bp.isDevelopment()) {
+                // Good.
+            } else if (bp.isRole()) {
+                if (!mayGrantRolePermission) {
+                    throw new SecurityException("Permission " + permName + " is managed by role");
+                }
+            } else {
+                throw new SecurityException("Permission " + permName + " requested by "
+                        + pkg.getPackageName() + " is not a changeable permission type");
+            }
+
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
+                return;
+            }
+
+            if (!(uidState.hasPermissionState(permName)
+                    || pkg.getRequestedPermissions().contains(permName))) {
+                throw new SecurityException("Package " + pkg.getPackageName()
+                        + " has not requested permission " + permName);
+            }
+
+            // If a permission review is required for legacy apps we represent
+            // their permissions as always granted runtime ones since we need
+            // to keep the review required permission flag per user while an
+            // install permission's state is shared across all users.
+            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) {
+                return;
+            }
+
+            final int flags = uidState.getPermissionFlags(permName);
+            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+                Log.e(TAG, "Cannot grant system fixed permission "
+                        + permName + " for package " + packageName);
+                return;
+            }
+            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+                Log.e(TAG, "Cannot grant policy fixed permission "
+                        + permName + " for package " + packageName);
+                return;
+            }
+
+            if (bp.isHardRestricted()
+                    && (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+                Log.e(TAG, "Cannot grant hard restricted non-exempt permission "
+                        + permName + " for package " + packageName);
+                return;
+            }
+
+            if (bp.isSoftRestricted() && !mayGrantSoftRestrictedPermission) {
+                Log.e(TAG, "Cannot grant soft restricted permission " + permName + " for package "
+                        + packageName);
+                return;
+            }
+
+            if (bp.isDevelopment() || bp.isRole()) {
+                // Development permissions must be handled specially, since they are not
+                // normal runtime permissions.  For now they apply to all users.
+                // TODO(zhanghai): We are breaking the behavior above by making all permission state
+                //  per-user. It isn't documented behavior and relatively rarely used anyway.
+                if (!uidState.grantPermission(bp)) {
+                    return;
+                }
+            } else {
+                if (ps.getUserStateOrDefault(userId).isInstantApp() && !bp.isInstant()) {
+                    throw new SecurityException("Cannot grant non-ephemeral permission" + permName
+                            + " for package " + packageName);
+                }
+
+                if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) {
+                    Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+                    return;
+                }
+
+                if (!uidState.grantPermission(bp)) {
+                    return;
+                }
+            }
+        }
+
+        if (isRuntimePermission) {
+            logPermission(MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED,
+                    permName, packageName);
+        }
+
+        final int uid = UserHandle.getUid(userId, pkg.getUid());
+        if (callback != null) {
+            if (isRuntimePermission) {
+                callback.onPermissionGranted(uid, userId);
+            } else {
+                callback.onInstallPermissionGranted();
+            }
+            if (permissionHasGids) {
+                callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
+            }
+        }
+
+        if (isRuntimePermission) {
+            notifyRuntimePermissionStateChanged(packageName, userId);
+        }
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
+        final int callingUid = Binder.getCallingUid();
+        final boolean overridePolicy =
+                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
+                        == PackageManager.PERMISSION_GRANTED;
+
+        revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId,
+                reason, mDefaultPermissionCallback);
+    }
+
+    private void revokeRuntimePermissionInternal(String packageName, String permName,
+            boolean overridePolicy, int callingUid, final int userId, String reason,
+            PermissionCallback callback) {
+        if (PermissionManager.DEBUG_TRACE_PERMISSION_UPDATES
+                && PermissionManager.shouldTraceGrant(packageName, permName, userId)) {
+            Log.i(TAG, "System is revoking " + packageName + " "
+                            + permName + " for user " + userId + " on behalf of uid " + callingUid
+                            + " " + mPackageManagerInt.getNameForUid(callingUid),
+                    new RuntimeException());
+        }
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                "revokeRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true,  // requireFullPermission
+                true,  // checkShell
+                "revokeRuntimePermission");
+
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            Log.e(TAG, "Unknown package: " + packageName);
+            return;
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        final boolean isRolePermission;
+        synchronized (mLock) {
+            final Permission permission = mRegistry.getPermission(permName);
+            if (permission == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permName);
+            }
+            isRolePermission = permission.isRole();
+        }
+        final boolean mayRevokeRolePermission = isRolePermission
+                // Allow ourselves to revoke role permissions due to definition changes.
+                && (callingUid == Process.myUid() || mayManageRolePermission(callingUid));
+
+        final boolean isRuntimePermission;
+        synchronized (mLock) {
+            final Permission bp = mRegistry.getPermission(permName);
+            if (bp == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permName);
+            }
+
+            isRuntimePermission = bp.isRuntime();
+            if (isRuntimePermission || bp.isDevelopment()) {
+                // Good.
+            } else if (bp.isRole()) {
+                if (!mayRevokeRolePermission) {
+                    throw new SecurityException("Permission " + permName + " is managed by role");
+                }
+            } else {
+                throw new SecurityException("Permission " + permName + " requested by "
+                        + pkg.getPackageName() + " is not a changeable permission type");
+            }
+
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
+                return;
+            }
+
+            if (!(uidState.hasPermissionState(permName)
+                    || pkg.getRequestedPermissions().contains(permName))) {
+                throw new SecurityException("Package " + pkg.getPackageName()
+                        + " has not requested permission " + permName);
+            }
+
+            // If a permission review is required for legacy apps we represent
+            // their permissions as always granted runtime ones since we need
+            // to keep the review required permission flag per user while an
+            // install permission's state is shared across all users.
+            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M && bp.isRuntime()) {
+                return;
+            }
+
+            final int flags = uidState.getPermissionFlags(permName);
+            // Only the system may revoke SYSTEM_FIXED permissions.
+            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                    && UserHandle.getCallingAppId() != Process.SYSTEM_UID) {
+                throw new SecurityException("Non-System UID cannot revoke system fixed permission "
+                        + permName + " for package " + packageName);
+            }
+            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+                throw new SecurityException("Cannot revoke policy fixed permission "
+                        + permName + " for package " + packageName);
+            }
+
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            // TODO(zhanghai): We are breaking the behavior above by making all permission state
+            //  per-user. It isn't documented behavior and relatively rarely used anyway.
+            if (!uidState.revokePermission(bp)) {
+                return;
+            }
+        }
+
+        if (isRuntimePermission) {
+            logPermission(MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED,
+                    permName, packageName);
+        }
+
+        if (callback != null) {
+            if (isRuntimePermission) {
+                callback.onPermissionRevoked(UserHandle.getUid(userId, pkg.getUid()), userId,
+                        reason);
+            } else {
+                mDefaultPermissionCallback.onInstallPermissionRevoked();
+            }
+        }
+
+        if (isRuntimePermission) {
+            notifyRuntimePermissionStateChanged(packageName, userId);
+        }
+    }
+
+    private boolean mayManageRolePermission(int uid) {
+        final PackageManager packageManager = mContext.getPackageManager();
+        final String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (packageNames == null) {
+            return false;
+        }
+        final String permissionControllerPackageName =
+                packageManager.getPermissionControllerPackageName();
+        return Arrays.asList(packageNames).contains(permissionControllerPackageName);
+    }
+
+    /**
+     * Reverts user permission state changes (permissions and flags).
+     *
+     * @param pkg The package for which to reset.
+     * @param userId The device user for which to do a reset.
+     */
+    private void resetRuntimePermissionsInternal(@NonNull AndroidPackage pkg,
+            @UserIdInt int userId) {
+        final String packageName = pkg.getPackageName();
+
+        // These are flags that can change base on user actions.
+        final int userSettableMask = FLAG_PERMISSION_USER_SET
+                | FLAG_PERMISSION_USER_FIXED
+                | FLAG_PERMISSION_REVOKED_COMPAT
+                | FLAG_PERMISSION_REVIEW_REQUIRED
+                | FLAG_PERMISSION_ONE_TIME
+                | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
+
+        final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
+                | FLAG_PERMISSION_POLICY_FIXED;
+
+        // Delay and combine non-async permission callbacks
+        final int permissionCount = ArrayUtils.size(pkg.getRequestedPermissions());
+        final boolean[] permissionRemoved = new boolean[1];
+        final ArraySet<Long> revokedPermissions = new ArraySet<>();
+        final IntArray syncUpdatedUsers = new IntArray(permissionCount);
+        final IntArray asyncUpdatedUsers = new IntArray(permissionCount);
+
+        PermissionCallback delayingPermCallback = new PermissionCallback() {
+            public void onGidsChanged(int appId, int userId) {
+                mDefaultPermissionCallback.onGidsChanged(appId, userId);
+            }
+
+            public void onPermissionChanged() {
+                mDefaultPermissionCallback.onPermissionChanged();
+            }
+
+            public void onPermissionGranted(int uid, int userId) {
+                mDefaultPermissionCallback.onPermissionGranted(uid, userId);
+            }
+
+            public void onInstallPermissionGranted() {
+                mDefaultPermissionCallback.onInstallPermissionGranted();
+            }
+
+            public void onPermissionRevoked(int uid, int userId, String reason) {
+                revokedPermissions.add(IntPair.of(uid, userId));
+
+                syncUpdatedUsers.add(userId);
+            }
+
+            public void onInstallPermissionRevoked() {
+                mDefaultPermissionCallback.onInstallPermissionRevoked();
+            }
+
+            public void onPermissionUpdated(int[] updatedUserIds, boolean sync) {
+                for (int userId : updatedUserIds) {
+                    if (sync) {
+                        syncUpdatedUsers.add(userId);
+                        asyncUpdatedUsers.remove(userId);
+                    } else {
+                        // Don't override sync=true by sync=false
+                        if (syncUpdatedUsers.indexOf(userId) == -1) {
+                            asyncUpdatedUsers.add(userId);
+                        }
+                    }
+                }
+            }
+
+            public void onPermissionRemoved() {
+                permissionRemoved[0] = true;
+            }
+
+            public void onInstallPermissionUpdated() {
+                mDefaultPermissionCallback.onInstallPermissionUpdated();
+            }
+
+            public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds,
+                    boolean sync, int uid) {
+                onPermissionUpdated(updatedUserIds, sync);
+                mOnPermissionChangeListeners.onPermissionsChanged(uid);
+            }
+
+            public void onInstallPermissionUpdatedNotifyListener(int uid) {
+                mDefaultPermissionCallback.onInstallPermissionUpdatedNotifyListener(uid);
+            }
+        };
+
+        for (int i = 0; i < permissionCount; i++) {
+            final String permName = pkg.getRequestedPermissions().get(i);
+
+            final boolean isRuntimePermission;
+            synchronized (mLock) {
+                final Permission permission = mRegistry.getPermission(permName);
+                if (permission == null) {
+                    continue;
+                }
+
+                if (permission.isRemoved()) {
+                    continue;
+                }
+                isRuntimePermission = permission.isRuntime();
+            }
+
+            // If shared user we just reset the state to which only this app contributed.
+            final String[] pkgNames = mPackageManagerInt.getSharedUserPackagesForPackage(
+                    pkg.getPackageName(), userId);
+            if (pkgNames.length > 0) {
+                boolean used = false;
+                for (String sharedPkgName : pkgNames) {
+                    final AndroidPackage sharedPkg =
+                            mPackageManagerInt.getPackage(sharedPkgName);
+                    if (sharedPkg != null && !sharedPkg.getPackageName().equals(packageName)
+                            && sharedPkg.getRequestedPermissions().contains(permName)) {
+                        used = true;
+                        break;
+                    }
+                }
+                if (used) {
+                    continue;
+                }
+            }
+
+            final int oldFlags =
+                    getPermissionFlagsInternal(packageName, permName, Process.SYSTEM_UID, userId);
+
+            // Always clear the user settable flags.
+            // If permission review is enabled and this is a legacy app, mark the
+            // permission as requiring a review as this is the initial state.
+            final int uid = mPackageManagerInt.getPackageUid(packageName, 0, userId);
+            final int targetSdk = mPackageManagerInt.getUidTargetSdkVersion(uid);
+            final int flags = (targetSdk < Build.VERSION_CODES.M && isRuntimePermission)
+                    ? FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKED_COMPAT
+                    : 0;
+
+            updatePermissionFlagsInternal(
+                    packageName, permName, userSettableMask, flags, Process.SYSTEM_UID, userId,
+                    false, delayingPermCallback);
+
+            // Below is only runtime permission handling.
+            if (!isRuntimePermission) {
+                continue;
+            }
+
+            // Never clobber system or policy.
+            if ((oldFlags & policyOrSystemFlags) != 0) {
+                continue;
+            }
+
+            // If this permission was granted by default or role, make sure it is.
+            if ((oldFlags & FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
+                    || (oldFlags & FLAG_PERMISSION_GRANTED_BY_ROLE) != 0) {
+                // PermissionPolicyService will handle the app op for runtime permissions later.
+                grantRuntimePermissionInternal(packageName, permName, false,
+                        Process.SYSTEM_UID, userId, delayingPermCallback);
+            // In certain cases we should leave the state unchanged:
+            // -- If permission review is enabled the permissions for a legacy apps
+            // are represented as constantly granted runtime ones
+            // -- If the permission was split from a non-runtime permission
+            } else if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0
+                    && !isPermissionSplitFromNonRuntime(permName, targetSdk)) {
+                // Otherwise, reset the permission.
+                revokeRuntimePermissionInternal(packageName, permName, false, Process.SYSTEM_UID,
+                        userId, null, delayingPermCallback);
+            }
+        }
+
+        // Execute delayed callbacks
+        if (permissionRemoved[0]) {
+            mDefaultPermissionCallback.onPermissionRemoved();
+        }
+
+        // Slight variation on the code in mPermissionCallback.onPermissionRevoked() as we cannot
+        // kill uid while holding mPackages-lock
+        if (!revokedPermissions.isEmpty()) {
+            int numRevokedPermissions = revokedPermissions.size();
+            for (int i = 0; i < numRevokedPermissions; i++) {
+                int revocationUID = IntPair.first(revokedPermissions.valueAt(i));
+                int revocationUserId = IntPair.second(revokedPermissions.valueAt(i));
+
+                mOnPermissionChangeListeners.onPermissionsChanged(revocationUID);
+
+                // Kill app later as we are holding mPackages
+                mHandler.post(() -> killUid(UserHandle.getAppId(revocationUID), revocationUserId,
+                        KILL_APP_REASON_PERMISSIONS_REVOKED));
+            }
+        }
+
+        mPackageManagerInt.writePermissionSettings(syncUpdatedUsers.toArray(), false);
+        mPackageManagerInt.writePermissionSettings(asyncUpdatedUsers.toArray(), true);
+    }
+
+    /**
+     * Determine if the given permission should be treated as split from a
+     * non-runtime permission for an application targeting the given SDK level.
+     */
+    private boolean isPermissionSplitFromNonRuntime(String permName, int targetSdk) {
+        final List<PermissionManager.SplitPermissionInfo> splitPerms = getSplitPermissionInfos();
+        final int size = splitPerms.size();
+        for (int i = 0; i < size; i++) {
+            final PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(i);
+            if (targetSdk < splitPerm.getTargetSdk()
+                    && splitPerm.getNewPermissions().contains(permName)) {
+                synchronized (mLock) {
+                    final Permission perm =
+                            mRegistry.getPermission(splitPerm.getSplitPermission());
+                    return perm != null && !perm.isRuntime();
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This change makes it so that apps are told to show rationale for asking for background
+     * location access every time they request.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
+    @Override
+    public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
+            @UserIdInt int userId) {
+        final int callingUid = Binder.getCallingUid();
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "canShowRequestPermissionRationale for user " + userId);
+        }
+
+        final int uid =
+                mPackageManagerInt.getPackageUid(packageName, MATCH_DEBUG_TRIAGED_MISSING, userId);
+        if (UserHandle.getAppId(callingUid) != UserHandle.getAppId(uid)) {
+            return false;
+        }
+
+        if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+
+        final int flags;
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED
+                | PackageManager.FLAG_PERMISSION_USER_FIXED;
+
+        if ((flags & fixedFlags) != 0) {
+            return false;
+        }
+
+        synchronized (mLock) {
+            final Permission permission = mRegistry.getPermission(permName);
+            if (permission == null) {
+                return false;
+            }
+            if (permission.isHardRestricted()
+                    && (flags & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
+                return false;
+            }
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+                    && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+                    packageName, userId)) {
+                return true;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "isPermissionRevokedByPolicy for user " + userId);
+        }
+
+        if (checkPermission(packageName, permName, userId) == PackageManager.PERMISSION_GRANTED) {
+            return false;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        if (mPackageManagerInt.filterAppAccess(packageName, callingUid, userId)) {
+            return false;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final int flags = getPermissionFlagsInternal(packageName, permName, callingUid, userId);
+            return (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Get the state of the runtime permissions as xml file.
+     *
+     * <p>Can not be called on main thread.
+     *
+     * @param userId The user ID the data should be extracted for
+     *
+     * @return The state as a xml file
+     */
+    @Nullable
+    @Override
+    public byte[] backupRuntimePermissions(@UserIdInt int userId) {
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        CompletableFuture<byte[]> backup = new CompletableFuture<>();
+        mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
+                mContext.getMainExecutor(), backup::complete);
+
+        try {
+            return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            Slog.e(TAG, "Cannot create permission backup for user " + userId, e);
+            return null;
+        }
+    }
+
+    /**
+     * Restore a permission state previously backed up via {@link #backupRuntimePermissions}.
+     *
+     * <p>If not all state can be restored, the un-appliable state will be delayed and can be
+     * applied via {@link #restoreDelayedRuntimePermissions}.
+     *
+     * @param backup The state as an xml file
+     * @param userId The user ID the data should be restored for
+     */
+    @Override
+    public void restoreRuntimePermissions(@NonNull byte[] backup, @UserIdInt int userId) {
+        Objects.requireNonNull(backup, "backup");
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        synchronized (mLock) {
+            mHasNoDelayedPermBackup.delete(userId);
+        }
+        mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup,
+                UserHandle.of(userId));
+    }
+
+    /**
+     * Try to apply permission backup that was previously not applied.
+     *
+     * <p>Can not be called on main thread.
+     *
+     * @param packageName The package that is newly installed
+     * @param userId The user ID the package is installed for
+     *
+     * @see #restoreRuntimePermissions
+     */
+    @Override
+    public void restoreDelayedRuntimePermissions(@NonNull String packageName,
+            @UserIdInt int userId) {
+        Objects.requireNonNull(packageName, "packageName");
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        synchronized (mLock) {
+            if (mHasNoDelayedPermBackup.get(userId, false)) {
+                return;
+            }
+        }
+        mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName,
+                UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> {
+                    if (hasMoreBackup) {
+                        return;
+                    }
+                    synchronized (mLock) {
+                        mHasNoDelayedPermBackup.put(userId, true);
+                    }
+                });
+    }
+
+    @Override
+    public void addOnRuntimePermissionStateChangedListener(
+            PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) {
+        synchronized (mLock) {
+            mRuntimePermissionStateChangedListeners.add(listener);
+        }
+    }
+
+    @Override
+    public void removeOnRuntimePermissionStateChangedListener(
+            PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener listener) {
+        synchronized (mLock) {
+            mRuntimePermissionStateChangedListeners.remove(listener);
+        }
+    }
+
+    private void notifyRuntimePermissionStateChanged(@NonNull String packageName,
+            @UserIdInt int userId) {
+        FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
+                PermissionManagerServiceImpl::doNotifyRuntimePermissionStateChanged,
+                PermissionManagerServiceImpl.this, packageName, userId));
+    }
+
+    private void doNotifyRuntimePermissionStateChanged(@NonNull String packageName,
+            @UserIdInt int userId) {
+        final ArrayList<PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener>
+                listeners;
+        synchronized (mLock) {
+            if (mRuntimePermissionStateChangedListeners.isEmpty()) {
+                return;
+            }
+            listeners = new ArrayList<>(mRuntimePermissionStateChangedListeners);
+        }
+        final int listenerCount = listeners.size();
+        for (int i = 0; i < listenerCount; i++) {
+            listeners.get(i).onRuntimePermissionStateChanged(packageName, userId);
+        }
+    }
+
+    /**
+     * If the app is updated, and has scoped storage permissions, then it is possible that the
+     * app updated in an attempt to get unscoped storage. If so, revoke all storage permissions.
+     * @param newPackage The new package that was installed
+     * @param oldPackage The old package that was updated
+     */
+    private void revokeStoragePermissionsIfScopeExpandedInternal(
+            @NonNull AndroidPackage newPackage,
+            @NonNull AndroidPackage oldPackage) {
+        boolean downgradedSdk = oldPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q
+                && newPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q;
+        boolean upgradedSdk = oldPackage.getTargetSdkVersion() < Build.VERSION_CODES.Q
+                && newPackage.getTargetSdkVersion() >= Build.VERSION_CODES.Q;
+        boolean newlyRequestsLegacy = !upgradedSdk && !oldPackage.isRequestLegacyExternalStorage()
+                && newPackage.isRequestLegacyExternalStorage();
+
+        if (!newlyRequestsLegacy && !downgradedSdk) {
+            return;
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        for (int userId: getAllUserIds()) {
+            int numRequestedPermissions = newPackage.getRequestedPermissions().size();
+            for (int i = 0; i < numRequestedPermissions; i++) {
+                PermissionInfo permInfo = getPermissionInfo(
+                        newPackage.getRequestedPermissions().get(i),
+                        newPackage.getPackageName(), 0);
+                if (permInfo == null || !STORAGE_PERMISSIONS.contains(permInfo.name)) {
+                    continue;
+                }
+
+                EventLog.writeEvent(0x534e4554, "171430330", newPackage.getUid(),
+                        "Revoking permission " + permInfo.name + " from package "
+                                + newPackage.getPackageName() + " as either the sdk downgraded "
+                                + downgradedSdk + " or newly requested legacy full storage "
+                                + newlyRequestsLegacy);
+
+                try {
+                    revokeRuntimePermissionInternal(newPackage.getPackageName(), permInfo.name,
+                            false, callingUid, userId, null, mDefaultPermissionCallback);
+                } catch (IllegalStateException | SecurityException e) {
+                    Log.e(TAG, "unable to revoke " + permInfo.name + " for "
+                            + newPackage.getPackageName() + " user " + userId, e);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * We might auto-grant permissions if any permission of the group is already granted. Hence if
+     * the group of a granted permission changes we need to revoke it to avoid having permissions of
+     * the new group auto-granted.
+     *
+     * @param newPackage The new package that was installed
+     * @param oldPackage The old package that was updated
+     */
+    private void revokeRuntimePermissionsIfGroupChangedInternal(@NonNull AndroidPackage newPackage,
+            @NonNull AndroidPackage oldPackage) {
+        final int numOldPackagePermissions = ArrayUtils.size(oldPackage.getPermissions());
+        final ArrayMap<String, String> oldPermissionNameToGroupName =
+                new ArrayMap<>(numOldPackagePermissions);
+
+        for (int i = 0; i < numOldPackagePermissions; i++) {
+            final ParsedPermission permission = oldPackage.getPermissions().get(i);
+
+            if (permission.getParsedPermissionGroup() != null) {
+                oldPermissionNameToGroupName.put(permission.getName(),
+                        permission.getParsedPermissionGroup().getName());
+            }
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        final int numNewPackagePermissions = ArrayUtils.size(newPackage.getPermissions());
+        for (int newPermissionNum = 0; newPermissionNum < numNewPackagePermissions;
+                newPermissionNum++) {
+            final ParsedPermission newPermission =
+                    newPackage.getPermissions().get(newPermissionNum);
+            final int newProtection = ParsedPermissionUtils.getProtection(newPermission);
+
+            if ((newProtection & PermissionInfo.PROTECTION_DANGEROUS) != 0) {
+                final String permissionName = newPermission.getName();
+                final String newPermissionGroupName =
+                        newPermission.getParsedPermissionGroup() == null
+                                ? null : newPermission.getParsedPermissionGroup().getName();
+                final String oldPermissionGroupName = oldPermissionNameToGroupName.get(
+                        permissionName);
+
+                if (newPermissionGroupName != null
+                        && !newPermissionGroupName.equals(oldPermissionGroupName)) {
+                    final int[] userIds = mUserManagerInt.getUserIds();
+                    mPackageManagerInt.forEachPackage(pkg -> {
+                        final String packageName = pkg.getPackageName();
+                        for (final int userId : userIds) {
+                            final int permissionState =
+                                    checkPermission(packageName, permissionName, userId);
+                            if (permissionState == PackageManager.PERMISSION_GRANTED) {
+                                EventLog.writeEvent(0x534e4554, "72710897",
+                                        newPackage.getUid(),
+                                        "Revoking permission " + permissionName +
+                                        " from package " + packageName +
+                                        " as the group changed from " + oldPermissionGroupName +
+                                        " to " + newPermissionGroupName);
+
+                                try {
+                                    revokeRuntimePermissionInternal(packageName, permissionName,
+                                            false, callingUid, userId, null,
+                                            mDefaultPermissionCallback);
+                                } catch (IllegalArgumentException e) {
+                                    Slog.e(TAG, "Could not revoke " + permissionName + " from "
+                                            + packageName, e);
+                                }
+                            }
+                        }
+                    });
+                }
+            }
+        }
+    }
+
+    /**
+     * If permissions are upgraded to runtime, or their owner changes to the system, then any
+     * granted permissions must be revoked.
+     *
+     * @param permissionsToRevoke A list of permission names to revoke
+     */
+    private void revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
+            @NonNull List<String> permissionsToRevoke) {
+        final int[] userIds = mUserManagerInt.getUserIds();
+        final int numPermissions = permissionsToRevoke.size();
+        final int callingUid = Binder.getCallingUid();
+
+        for (int permNum = 0; permNum < numPermissions; permNum++) {
+            final String permName = permissionsToRevoke.get(permNum);
+            final boolean isInternalPermission;
+            synchronized (mLock) {
+                final Permission bp = mRegistry.getPermission(permName);
+                if (bp == null || !(bp.isInternal() || bp.isRuntime())) {
+                    continue;
+                }
+                isInternalPermission = bp.isInternal();
+            }
+            mPackageManagerInt.forEachPackage(pkg -> {
+                final String packageName = pkg.getPackageName();
+                final int appId = pkg.getUid();
+                if (appId < Process.FIRST_APPLICATION_UID) {
+                    // do not revoke from system apps
+                    return;
+                }
+                for (final int userId : userIds) {
+                    final int permissionState = checkPermission(packageName, permName,
+                            userId);
+                    final int flags = getPermissionFlags(packageName, permName, userId);
+                    final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
+                            | FLAG_PERMISSION_POLICY_FIXED
+                            | FLAG_PERMISSION_GRANTED_BY_DEFAULT
+                            | FLAG_PERMISSION_GRANTED_BY_ROLE;
+                    if (permissionState == PackageManager.PERMISSION_GRANTED
+                            && (flags & flagMask) == 0) {
+                        final int uid = UserHandle.getUid(userId, appId);
+                        if (isInternalPermission) {
+                            EventLog.writeEvent(0x534e4554, "195338390", uid,
+                                    "Revoking permission " + permName + " from package "
+                                            + packageName + " due to definition change");
+                        } else {
+                            EventLog.writeEvent(0x534e4554, "154505240", uid,
+                                    "Revoking permission " + permName + " from package "
+                                            + packageName + " due to definition change");
+                            EventLog.writeEvent(0x534e4554, "168319670", uid,
+                                    "Revoking permission " + permName + " from package "
+                                            + packageName + " due to definition change");
+                        }
+                        Slog.e(TAG, "Revoking permission " + permName + " from package "
+                                + packageName + " due to definition change");
+                        try {
+                            revokeRuntimePermissionInternal(packageName, permName,
+                                    false, callingUid, userId, null, mDefaultPermissionCallback);
+                        } catch (Exception e) {
+                            Slog.e(TAG, "Could not revoke " + permName + " from "
+                                    + packageName, e);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    private List<String> addAllPermissionsInternal(@NonNull AndroidPackage pkg) {
+        final int N = ArrayUtils.size(pkg.getPermissions());
+        ArrayList<String> definitionChangedPermissions = new ArrayList<>();
+        for (int i=0; i<N; i++) {
+            ParsedPermission p = pkg.getPermissions().get(i);
+
+            // Assume by default that we did not install this permission into the system.
+            ComponentMutateUtils.setExactFlags(p, p.getFlags() & ~PermissionInfo.FLAG_INSTALLED);
+
+            final PermissionInfo permissionInfo;
+            final Permission oldPermission;
+            synchronized (mLock) {
+                // Now that permission groups have a special meaning, we ignore permission
+                // groups for legacy apps to prevent unexpected behavior. In particular,
+                // permissions for one app being granted to someone just because they happen
+                // to be in a group defined by another app (before this had no implications).
+                if (pkg.getTargetSdkVersion() > Build.VERSION_CODES.LOLLIPOP_MR1) {
+                    ComponentMutateUtils.setParsedPermissionGroup(p,
+                            mRegistry.getPermissionGroup(p.getGroup()));
+                    // Warn for a permission in an unknown group.
+                    if (DEBUG_PERMISSIONS
+                            && p.getGroup() != null && p.getParsedPermissionGroup() == null) {
+                        Slog.i(TAG, "Permission " + p.getName() + " from package "
+                                + p.getPackageName() + " in an unknown group " + p.getGroup());
+                    }
+                }
+
+                permissionInfo = PackageInfoUtils.generatePermissionInfo(p,
+                        PackageManager.GET_META_DATA);
+                oldPermission = p.isTree() ? mRegistry.getPermissionTree(p.getName())
+                        : mRegistry.getPermission(p.getName());
+            }
+            // TODO(zhanghai): Maybe we should store whether a permission is owned by system inside
+            //  itself.
+            final boolean isOverridingSystemPermission = Permission.isOverridingSystemPermission(
+                    oldPermission, permissionInfo, mPackageManagerInt);
+            synchronized (mLock) {
+                final Permission permission = Permission.createOrUpdate(oldPermission,
+                        permissionInfo, pkg, mRegistry.getPermissionTrees(),
+                        isOverridingSystemPermission);
+                if (p.isTree()) {
+                    mRegistry.addPermissionTree(permission);
+                } else {
+                    mRegistry.addPermission(permission);
+                }
+                if (permission.isInstalled()) {
+                    ComponentMutateUtils.setExactFlags(p,
+                            p.getFlags() | PermissionInfo.FLAG_INSTALLED);
+                }
+                if (permission.isDefinitionChanged()) {
+                    definitionChangedPermissions.add(p.getName());
+                    permission.setDefinitionChanged(false);
+                }
+            }
+        }
+        return definitionChangedPermissions;
+    }
+
+    private void addAllPermissionGroupsInternal(@NonNull AndroidPackage pkg) {
+        synchronized (mLock) {
+            final int N = ArrayUtils.size(pkg.getPermissionGroups());
+            StringBuilder r = null;
+            for (int i = 0; i < N; i++) {
+                final ParsedPermissionGroup pg = pkg.getPermissionGroups().get(i);
+                final ParsedPermissionGroup cur = mRegistry.getPermissionGroup(pg.getName());
+                final String curPackageName = (cur == null) ? null : cur.getPackageName();
+                final boolean isPackageUpdate = pg.getPackageName().equals(curPackageName);
+                if (cur == null || isPackageUpdate) {
+                    mRegistry.addPermissionGroup(pg);
+                    if (DEBUG_PACKAGE_SCANNING) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        if (isPackageUpdate) {
+                            r.append("UPD:");
+                        }
+                        r.append(pg.getName());
+                    }
+                } else {
+                    Slog.w(TAG, "Permission group " + pg.getName() + " from package "
+                            + pg.getPackageName() + " ignored: original from "
+                            + cur.getPackageName());
+                    if (DEBUG_PACKAGE_SCANNING) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append("DUP:");
+                        r.append(pg.getName());
+                    }
+                }
+            }
+            if (r != null && DEBUG_PACKAGE_SCANNING) {
+                Log.d(TAG, "  Permission Groups: " + r);
+            }
+        }
+    }
+
+    private void removeAllPermissionsInternal(@NonNull AndroidPackage pkg) {
+        synchronized (mLock) {
+            int n = ArrayUtils.size(pkg.getPermissions());
+            StringBuilder r = null;
+            for (int i = 0; i < n; i++) {
+                ParsedPermission p = pkg.getPermissions().get(i);
+                Permission bp = mRegistry.getPermission(p.getName());
+                if (bp == null) {
+                    bp = mRegistry.getPermissionTree(p.getName());
+                }
+                if (bp != null && bp.isPermission(p)) {
+                    bp.setPermissionInfo(null);
+                    if (DEBUG_REMOVE) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(p.getName());
+                    }
+                }
+                if (ParsedPermissionUtils.isAppOp(p)) {
+                    // TODO(zhanghai): Should we just remove the entry for this permission directly?
+                    mRegistry.removeAppOpPermissionPackage(p.getName(), pkg.getPackageName());
+                }
+            }
+            if (r != null) {
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+            }
+
+            n = pkg.getRequestedPermissions().size();
+            r = null;
+            for (int i = 0; i < n; i++) {
+                final String permissionName = pkg.getRequestedPermissions().get(i);
+                final Permission permission = mRegistry.getPermission(permissionName);
+                if (permission != null && permission.isAppOp()) {
+                    mRegistry.removeAppOpPermissionPackage(permissionName,
+                            pkg.getPackageName());
+                }
+            }
+            if (r != null) {
+                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+            }
+        }
+    }
+
+    @Override
+    public void onUserRemoved(@UserIdInt int userId) {
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        synchronized (mLock) {
+            mState.removeUserState(userId);
+        }
+    }
+
+    @NonNull
+    private Set<String> getGrantedPermissionsInternal(@NonNull String packageName,
+            @UserIdInt int userId) {
+        final PackageStateInternal ps = mPackageManagerInt.getPackageStateInternal(packageName);
+        if (ps == null) {
+            return Collections.emptySet();
+        }
+
+        synchronized (mLock) {
+            final UidPermissionState uidState = getUidStateLocked(ps, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + packageName + " and user " + userId);
+                return Collections.emptySet();
+            }
+            if (!ps.getUserStateOrDefault(userId).isInstantApp()) {
+                return uidState.getGrantedPermissions();
+            } else {
+                // Install permission state is shared among all users, but instant app state is
+                // per-user, so we can only filter it here unless we make install permission state
+                // per-user as well.
+                final Set<String> instantPermissions =
+                        new ArraySet<>(uidState.getGrantedPermissions());
+                instantPermissions.removeIf(permissionName -> {
+                    Permission permission = mRegistry.getPermission(permissionName);
+                    if (permission == null) {
+                        return true;
+                    }
+                    if (!permission.isInstant()) {
+                        EventLog.writeEvent(0x534e4554, "140256621", UserHandle.getUid(userId,
+                                ps.getAppId()), permissionName);
+                        return true;
+                    }
+                    return false;
+                });
+                return instantPermissions;
+            }
+        }
+    }
+
+    @NonNull
+    private int[] getPermissionGidsInternal(@NonNull String permissionName, @UserIdInt int userId) {
+        synchronized (mLock) {
+            Permission permission = mRegistry.getPermission(permissionName);
+            if (permission == null) {
+                return EmptyArray.INT;
+            }
+            return permission.computeGids(userId);
+        }
+    }
+
+    /**
+     * Restore the permission state for a package.
+     *
+     * <ul>
+     *     <li>During boot the state gets restored from the disk</li>
+     *     <li>During app update the state gets restored from the last version of the app</li>
+     * </ul>
+     *
+     * @param pkg the package the permissions belong to
+     * @param replace if the package is getting replaced (this might change the requested
+     *                permissions of this package)
+     * @param packageOfInterest If this is the name of {@code pkg} add extra logging
+     * @param callback Result call back
+     * @param filterUserId If not {@link UserHandle.USER_ALL}, only restore the permission state for
+     *                     this particular user
+     */
+    private void restorePermissionState(@NonNull AndroidPackage pkg, boolean replace,
+            @Nullable String packageOfInterest, @Nullable PermissionCallback callback,
+            @UserIdInt int filterUserId) {
+        // IMPORTANT: There are two types of permissions: install and runtime.
+        // Install time permissions are granted when the app is installed to
+        // all device users and users added in the future. Runtime permissions
+        // are granted at runtime explicitly to specific users. Normal and signature
+        // protected permissions are install time permissions. Dangerous permissions
+        // are install permissions if the app's target SDK is Lollipop MR1 or older,
+        // otherwise they are runtime permissions. This function does not manage
+        // runtime permissions except for the case an app targeting Lollipop MR1
+        // being upgraded to target a newer SDK, in which case dangerous permissions
+        // are transformed from install time to runtime ones.
+
+        final PackageStateInternal ps =
+                mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+        if (ps == null) {
+            return;
+        }
+
+        final int[] userIds = filterUserId == UserHandle.USER_ALL ? getAllUserIds()
+                : new int[] { filterUserId };
+
+        boolean runtimePermissionsRevoked = false;
+        int[] updatedUserIds = EMPTY_INT_ARRAY;
+
+        ArraySet<String> isPrivilegedPermissionAllowlisted = null;
+        ArraySet<String> shouldGrantSignaturePermission = null;
+        ArraySet<String> shouldGrantInternalPermission = null;
+        ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted = new ArraySet<>();
+        final List<String> requestedPermissions = pkg.getRequestedPermissions();
+        final int requestedPermissionsSize = requestedPermissions.size();
+        for (int i = 0; i < requestedPermissionsSize; i++) {
+            final String permissionName = pkg.getRequestedPermissions().get(i);
+
+            final Permission permission;
+            synchronized (mLock) {
+                permission = mRegistry.getPermission(permissionName);
+            }
+            if (permission == null) {
+                continue;
+            }
+            if (permission.isPrivileged()
+                    && checkPrivilegedPermissionAllowlist(pkg, ps, permission)) {
+                if (isPrivilegedPermissionAllowlisted == null) {
+                    isPrivilegedPermissionAllowlisted = new ArraySet<>();
+                }
+                isPrivilegedPermissionAllowlisted.add(permissionName);
+            }
+            if (permission.isSignature() && (shouldGrantPermissionBySignature(pkg, permission)
+                    || shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+                            shouldGrantPrivilegedPermissionIfWasGranted))) {
+                if (shouldGrantSignaturePermission == null) {
+                    shouldGrantSignaturePermission = new ArraySet<>();
+                }
+                shouldGrantSignaturePermission.add(permissionName);
+            }
+            if (permission.isInternal()
+                    && shouldGrantPermissionByProtectionFlags(pkg, ps, permission,
+                            shouldGrantPrivilegedPermissionIfWasGranted)) {
+                if (shouldGrantInternalPermission == null) {
+                    shouldGrantInternalPermission = new ArraySet<>();
+                }
+                shouldGrantInternalPermission.add(permissionName);
+            }
+        }
+
+        final SparseBooleanArray isPermissionPolicyInitialized = new SparseBooleanArray();
+        if (mPermissionPolicyInternal != null) {
+            for (final int userId : userIds) {
+                if (mPermissionPolicyInternal.isInitialized(userId)) {
+                    isPermissionPolicyInitialized.put(userId, true);
+                }
+            }
+        }
+
+        synchronized (mLock) {
+            for (final int userId : userIds) {
+                final UserPermissionState userState = mState.getOrCreateUserState(userId);
+                final UidPermissionState uidState = userState.getOrCreateUidState(ps.getAppId());
+
+                if (uidState.isMissing()) {
+                    Collection<String> uidRequestedPermissions;
+                    int targetSdkVersion;
+                    if (ps.getSharedUser() == null) {
+                        uidRequestedPermissions = pkg.getRequestedPermissions();
+                        targetSdkVersion = pkg.getTargetSdkVersion();
+                    } else {
+                        uidRequestedPermissions = new ArraySet<>();
+                        targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+                        List<AndroidPackage> packages = ps.getSharedUser().getPackages();
+                        int packagesSize = packages.size();
+                        for (int i = 0; i < packagesSize; i++) {
+                            AndroidPackage sharedUserPackage = packages.get(i);
+                            uidRequestedPermissions.addAll(
+                                    sharedUserPackage.getRequestedPermissions());
+                            targetSdkVersion = Math.min(targetSdkVersion,
+                                    sharedUserPackage.getTargetSdkVersion());
+                        }
+                    }
+
+                    for (String permissionName : uidRequestedPermissions) {
+                        Permission permission = mRegistry.getPermission(permissionName);
+                        if (permission == null) {
+                            continue;
+                        }
+                        if (Objects.equals(permission.getPackageName(), PLATFORM_PACKAGE_NAME)
+                                && permission.isRuntime() && !permission.isRemoved()) {
+                            if (permission.isHardOrSoftRestricted()
+                                    || permission.isImmutablyRestricted()) {
+                                uidState.updatePermissionFlags(permission,
+                                        FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT,
+                                        FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT);
+                            }
+                            if (targetSdkVersion < Build.VERSION_CODES.M) {
+                                uidState.updatePermissionFlags(permission,
+                                        PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                                                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                        PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                                                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT);
+                                uidState.grantPermission(permission);
+                            }
+                        }
+                    }
+
+                    uidState.setMissing(false);
+                    updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                }
+
+                UidPermissionState origState = uidState;
+
+                boolean changedInstallPermission = false;
+
+                if (replace) {
+                    userState.setInstallPermissionsFixed(ps.getPackageName(), false);
+                    if (ps.getSharedUser() == null) {
+                        origState = new UidPermissionState(uidState);
+                        uidState.reset();
+                    } else {
+                        // We need to know only about runtime permission changes since the
+                        // calling code always writes the install permissions state but
+                        // the runtime ones are written only if changed. The only cases of
+                        // changed runtime permissions here are promotion of an install to
+                        // runtime and revocation of a runtime from a shared user.
+                        if (revokeUnusedSharedUserPermissionsLocked(
+                                ps.getSharedUser().getPackages(), uidState)) {
+                            updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                            runtimePermissionsRevoked = true;
+                        }
+                    }
+                }
+
+                ArraySet<String> newImplicitPermissions = new ArraySet<>();
+                final String friendlyName = pkg.getPackageName() + "(" + pkg.getUid() + ")";
+
+                for (int i = 0; i < requestedPermissionsSize; i++) {
+                    final String permName = requestedPermissions.get(i);
+
+                    final Permission bp = mRegistry.getPermission(permName);
+                    final boolean appSupportsRuntimePermissions =
+                            pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
+                    String legacyActivityRecognitionPermission = null;
+
+                    if (DEBUG_INSTALL && bp != null) {
+                        Log.i(TAG, "Package " + friendlyName
+                                + " checking " + permName + ": " + bp);
+                    }
+
+                    // TODO(zhanghai): I don't think we need to check source package setting if
+                    //  permission is present, because otherwise the permission should have been
+                    //  removed.
+                    if (bp == null /*|| getSourcePackageSetting(bp) == null*/) {
+                        if (packageOfInterest == null || packageOfInterest.equals(
+                                pkg.getPackageName())) {
+                            if (DEBUG_PERMISSIONS) {
+                                Slog.i(TAG, "Unknown permission " + permName
+                                        + " in package " + friendlyName);
+                            }
+                        }
+                        continue;
+                    }
+
+                    // Cache newImplicitPermissions before modifing permissionsState as for the
+                    // shared uids the original and new state are the same object
+                    if (!origState.hasPermissionState(permName)
+                            && (pkg.getImplicitPermissions().contains(permName)
+                            || (permName.equals(Manifest.permission.ACTIVITY_RECOGNITION)))) {
+                        if (pkg.getImplicitPermissions().contains(permName)) {
+                            // If permName is an implicit permission, try to auto-grant
+                            newImplicitPermissions.add(permName);
+
+                            if (DEBUG_PERMISSIONS) {
+                                Slog.i(TAG, permName + " is newly added for " + friendlyName);
+                            }
+                        } else {
+                            // Special case for Activity Recognition permission. Even if AR
+                            // permission is not an implicit permission we want to add it to the
+                            // list (try to auto-grant it) if the app was installed on a device
+                            // before AR permission was split, regardless of if the app now requests
+                            // the new AR permission or has updated its target SDK and AR is no
+                            // longer implicit to it. This is a compatibility workaround for apps
+                            // when AR permission was split in Q.
+                            // TODO(zhanghai): This calls into SystemConfig, which generally
+                            //  shouldn't  cause deadlock, but maybe we should keep a cache of the
+                            //  split permission  list and just eliminate the possibility.
+                            final List<PermissionManager.SplitPermissionInfo> permissionList =
+                                    getSplitPermissionInfos();
+                            int numSplitPerms = permissionList.size();
+                            for (int splitPermNum = 0; splitPermNum < numSplitPerms;
+                                    splitPermNum++) {
+                                PermissionManager.SplitPermissionInfo sp = permissionList.get(
+                                        splitPermNum);
+                                String splitPermName = sp.getSplitPermission();
+                                if (sp.getNewPermissions().contains(permName)
+                                        && origState.isPermissionGranted(splitPermName)) {
+                                    legacyActivityRecognitionPermission = splitPermName;
+                                    newImplicitPermissions.add(permName);
+
+                                    if (DEBUG_PERMISSIONS) {
+                                        Slog.i(TAG, permName + " is newly added for "
+                                                + friendlyName);
+                                    }
+                                    break;
+                                }
+                            }
+                        }
+                    }
+
+                    // TODO(b/140256621): The package instant app method has been removed
+                    //  as part of work in b/135203078, so this has been commented out in the
+                    //  meantime
+                    // Limit ephemeral apps to ephemeral allowed permissions.
+        //            if (/*pkg.isInstantApp()*/ false && !bp.isInstant()) {
+        //                if (DEBUG_PERMISSIONS) {
+        //                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName()
+        //                            + " for package " + pkg.getPackageName());
+        //                }
+        //                continue;
+        //            }
+
+                    if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
+                        if (DEBUG_PERMISSIONS) {
+                            Log.i(TAG, "Denying runtime-only permission " + bp.getName()
+                                    + " for package " + friendlyName);
+                        }
+                        continue;
+                    }
+
+                    final String perm = bp.getName();
+
+                    // Keep track of app op permissions.
+                    if (bp.isAppOp()) {
+                        mRegistry.addAppOpPermissionPackage(perm, pkg.getPackageName());
+                    }
+
+                    boolean shouldGrantNormalPermission = true;
+                    if (bp.isNormal() && !origState.isPermissionGranted(perm)) {
+                        // If this is an existing, non-system package, then
+                        // we can't add any new permissions to it. Runtime
+                        // permissions can be added any time - they are dynamic.
+                        if (!ps.isSystem() && userState.areInstallPermissionsFixed(
+                                ps.getPackageName())) {
+                            // Except...  if this is a permission that was added
+                            // to the platform (note: need to only do this when
+                            // updating the platform).
+                            if (!isCompatPlatformPermissionForPackage(perm, pkg)) {
+                                shouldGrantNormalPermission = false;
+                            }
+                        }
+                    }
+
+                    if (DEBUG_PERMISSIONS) {
+                        Slog.i(TAG, "Considering granting permission " + perm + " to package "
+                                + pkg.getPackageName());
+                    }
+
+                    if ((bp.isNormal() && shouldGrantNormalPermission)
+                            || (bp.isSignature()
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantSignaturePermission,
+                                            permName)
+                                            || (((bp.isPrivileged() && CollectionUtils.contains(
+                                                    shouldGrantPrivilegedPermissionIfWasGranted,
+                                                    permName)) || bp.isDevelopment() || bp.isRole())
+                                                    && origState.isPermissionGranted(permName))))
+                            || (bp.isInternal()
+                                    && (!bp.isPrivileged() || CollectionUtils.contains(
+                                            isPrivilegedPermissionAllowlisted, permName))
+                                    && (CollectionUtils.contains(shouldGrantInternalPermission,
+                                            permName)
+                                            || (((bp.isPrivileged() && CollectionUtils.contains(
+                                                    shouldGrantPrivilegedPermissionIfWasGranted,
+                                                    permName)) || bp.isDevelopment() || bp.isRole())
+                                                    && origState.isPermissionGranted(permName))))) {
+                        // Grant an install permission.
+                        if (uidState.grantPermission(bp)) {
+                            changedInstallPermission = true;
+                        }
+                    } else if (bp.isRuntime()) {
+                        boolean hardRestricted = bp.isHardRestricted();
+                        boolean softRestricted = bp.isSoftRestricted();
+
+                        // If permission policy is not ready we don't deal with restricted
+                        // permissions as the policy may allowlist some permissions. Once
+                        // the policy is initialized we would re-evaluate permissions.
+                        final boolean permissionPolicyInitialized =
+                                isPermissionPolicyInitialized.get(userId);
+
+                        PermissionState origPermState = origState.getPermissionState(perm);
+                        int flags = origPermState != null ? origPermState.getFlags() : 0;
+
+                        boolean wasChanged = false;
+
+                        boolean restrictionExempt =
+                                (origState.getPermissionFlags(bp.getName())
+                                        & FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
+                        boolean restrictionApplied = (origState.getPermissionFlags(
+                                bp.getName()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+
+                        if (appSupportsRuntimePermissions) {
+                            // If hard restricted we don't allow holding it
+                            if (permissionPolicyInitialized && hardRestricted) {
+                                if (!restrictionExempt) {
+                                    if (origPermState != null && origPermState.isGranted()
+                                            && uidState.revokePermission(bp)) {
+                                        wasChanged = true;
+                                    }
+                                    if (!restrictionApplied) {
+                                        flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+                                        wasChanged = true;
+                                    }
+                                }
+                            // If soft restricted we allow holding in a restricted form
+                            } else if (permissionPolicyInitialized && softRestricted) {
+                                // Regardless if granted set the restriction flag as it
+                                // may affect app treatment based on this permission.
+                                if (!restrictionExempt && !restrictionApplied) {
+                                    flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+                                    wasChanged = true;
+                                }
+                            }
+
+                            // Remove review flag as it is not necessary anymore
+                            if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                                flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED;
+                                wasChanged = true;
+                            }
+
+                            if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0
+                                    && !isPermissionSplitFromNonRuntime(permName,
+                                    pkg.getTargetSdkVersion())) {
+                                flags &= ~FLAG_PERMISSION_REVOKED_COMPAT;
+                                wasChanged = true;
+                            // Hard restricted permissions cannot be held.
+                            } else if (!permissionPolicyInitialized
+                                    || (!hardRestricted || restrictionExempt)) {
+                                if ((origPermState != null && origPermState.isGranted())
+                                        || legacyActivityRecognitionPermission != null) {
+                                    if (!uidState.grantPermission(bp)) {
+                                        wasChanged = true;
+                                    }
+                                }
+                            }
+                        } else {
+                            if (origPermState == null) {
+                                // New permission
+                                if (PLATFORM_PACKAGE_NAME.equals(
+                                        bp.getPackageName())) {
+                                    if (!bp.isRemoved()) {
+                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED
+                                                | FLAG_PERMISSION_REVOKED_COMPAT;
+                                        wasChanged = true;
+                                    }
+                                }
+                            }
+
+                            if (!uidState.isPermissionGranted(bp.getName())
+                                    && uidState.grantPermission(bp)) {
+                                wasChanged = true;
+                            }
+
+                            // If legacy app always grant the permission but if restricted
+                            // and not exempt take a note a restriction should be applied.
+                            if (permissionPolicyInitialized
+                                    && (hardRestricted || softRestricted)
+                                            && !restrictionExempt && !restrictionApplied) {
+                                flags |= FLAG_PERMISSION_APPLY_RESTRICTION;
+                                wasChanged = true;
+                            }
+                        }
+
+                        // If unrestricted or restriction exempt, don't apply restriction.
+                        if (permissionPolicyInitialized) {
+                            if (!(hardRestricted || softRestricted) || restrictionExempt) {
+                                if (restrictionApplied) {
+                                    flags &= ~FLAG_PERMISSION_APPLY_RESTRICTION;
+                                    // Dropping restriction on a legacy app implies a review
+                                    if (!appSupportsRuntimePermissions) {
+                                        flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
+                                    }
+                                    wasChanged = true;
+                                }
+                            }
+                        }
+
+                        if (wasChanged) {
+                            updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                        }
+
+                        uidState.updatePermissionFlags(bp, MASK_PERMISSION_FLAGS_ALL,
+                                flags);
+                    } else {
+                        if (DEBUG_PERMISSIONS) {
+                            boolean wasGranted = uidState.isPermissionGranted(bp.getName());
+                            if (wasGranted || bp.isAppOp()) {
+                                Slog.i(TAG, (wasGranted ? "Un-granting" : "Not granting")
+                                        + " permission " + perm
+                                        + " from package " + friendlyName
+                                        + " (protectionLevel=" + bp.getProtectionLevel()
+                                        + " flags=0x"
+                                        + Integer.toHexString(PackageInfoUtils.appInfoFlags(pkg,
+                                                ps))
+                                        + ")");
+                            }
+                        }
+                        if (uidState.removePermissionState(bp.getName())) {
+                            changedInstallPermission = true;
+                        }
+                    }
+                }
+
+                if ((changedInstallPermission || replace)
+                        && !userState.areInstallPermissionsFixed(ps.getPackageName())
+                        && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) {
+                    // This is the first that we have heard about this package, so the
+                    // permissions we have now selected are fixed until explicitly
+                    // changed.
+                    userState.setInstallPermissionsFixed(ps.getPackageName(), true);
+                }
+
+                updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg,
+                        userId, updatedUserIds);
+                updatedUserIds = setInitialGrantForNewImplicitPermissionsLocked(origState,
+                        uidState, pkg, newImplicitPermissions, userId, updatedUserIds);
+            }
+        }
+
+        updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, userIds,
+                updatedUserIds);
+
+        // TODO: Kill UIDs whose GIDs or runtime permissions changed. This might be more important
+        //  for shared users.
+        // Persist the runtime permissions state for users with changes. If permissions
+        // were revoked because no app in the shared user declares them we have to
+        // write synchronously to avoid losing runtime permissions state.
+        if (callback != null) {
+            callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked);
+        }
+
+        for (int userId : updatedUserIds) {
+            notifyRuntimePermissionStateChanged(pkg.getPackageName(), userId);
+        }
+    }
+
+    /**
+     * Returns all relevant user ids.  This list include the current set of created user ids as well
+     * as pre-created user ids.
+     * @return user ids for created users and pre-created users
+     */
+    private int[] getAllUserIds() {
+        return UserManagerService.getInstance().getUserIdsIncludingPreCreated();
+    }
+
+    /**
+     * Revoke permissions that are not implicit anymore and that have
+     * {@link PackageManager#FLAG_PERMISSION_REVOKE_WHEN_REQUESTED} set.
+     *
+     * @param ps The state of the permissions of the package
+     * @param pkg The package that is currently looked at
+     * @param userIds All user IDs in the system, must be passed in because this method is locked
+     * @param updatedUserIds a list of user ids that needs to be amended if the permission state
+     *                       for a user is changed.
+     *
+     * @return The updated value of the {@code updatedUserIds} parameter
+     */
+    @NonNull
+    @GuardedBy("mLock")
+    private int[] revokePermissionsNoLongerImplicitLocked(@NonNull UidPermissionState ps,
+            @NonNull AndroidPackage pkg, int userId, @NonNull int[] updatedUserIds) {
+        String pkgName = pkg.getPackageName();
+        boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
+                >= Build.VERSION_CODES.M;
+
+        for (String permission : ps.getGrantedPermissions()) {
+            if (pkg.getRequestedPermissions().contains(permission)
+                    && !pkg.getImplicitPermissions().contains(permission)) {
+                Permission bp = mRegistry.getPermission(permission);
+                if (bp != null && bp.isRuntime()) {
+                    int flags = ps.getPermissionFlags(permission);
+                    if ((flags & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0) {
+                        int flagsToRemove = FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+
+                        // We're willing to preserve an implicit "Nearby devices"
+                        // permission grant if this app was already able to interact
+                        // with nearby devices via background location access
+                        boolean preserveGrant = false;
+                        if (ArrayUtils.contains(NEARBY_DEVICES_PERMISSIONS, permission)
+                                && ps.isPermissionGranted(
+                                        android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+                                && (ps.getPermissionFlags(
+                                        android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+                                        & (FLAG_PERMISSION_REVOKE_WHEN_REQUESTED
+                                                | FLAG_PERMISSION_REVOKED_COMPAT)) == 0) {
+                            preserveGrant = true;
+                        }
+
+                        if ((flags & BLOCKING_PERMISSION_FLAGS) == 0
+                                && supportsRuntimePermissions
+                                && !preserveGrant) {
+                            if (ps.revokePermission(bp)) {
+                                if (DEBUG_PERMISSIONS) {
+                                    Slog.i(TAG, "Revoking runtime permission "
+                                            + permission + " for " + pkgName
+                                            + " as it is now requested");
+                                }
+                            }
+
+                            flagsToRemove |= USER_PERMISSION_FLAGS;
+                        }
+
+                        ps.updatePermissionFlags(bp, flagsToRemove, 0);
+                        updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                    }
+                }
+            }
+        }
+
+        return updatedUserIds;
+    }
+
+    /**
+     * {@code newPerm} is newly added; Inherit the state from {@code sourcePerms}.
+     *
+     * <p>A single new permission can be split off from several source permissions. In this case
+     * the most leniant state is inherited.
+     *
+     * <p>Warning: This does not handle foreground / background permissions
+     *
+     * @param sourcePerms The permissions to inherit from
+     * @param newPerm The permission to inherit to
+     * @param ps The permission state of the package
+     * @param pkg The package requesting the permissions
+     */
+    @GuardedBy("mLock")
+    private void inheritPermissionStateToNewImplicitPermissionLocked(
+            @NonNull ArraySet<String> sourcePerms, @NonNull String newPerm,
+            @NonNull UidPermissionState ps, @NonNull AndroidPackage pkg) {
+        String pkgName = pkg.getPackageName();
+        boolean isGranted = false;
+        int flags = 0;
+
+        int numSourcePerm = sourcePerms.size();
+        for (int i = 0; i < numSourcePerm; i++) {
+            String sourcePerm = sourcePerms.valueAt(i);
+            if (ps.isPermissionGranted(sourcePerm)) {
+                if (!isGranted) {
+                    flags = 0;
+                }
+
+                isGranted = true;
+                flags |= ps.getPermissionFlags(sourcePerm);
+            } else {
+                if (!isGranted) {
+                    flags |= ps.getPermissionFlags(sourcePerm);
+                }
+            }
+        }
+
+        if (isGranted) {
+            if (DEBUG_PERMISSIONS) {
+                Slog.i(TAG, newPerm + " inherits runtime perm grant from " + sourcePerms
+                        + " for " + pkgName);
+            }
+
+            ps.grantPermission(mRegistry.getPermission(newPerm));
+        }
+
+        // Add permission flags
+        ps.updatePermissionFlags(mRegistry.getPermission(newPerm), flags, flags);
+    }
+
+    /**
+     * When the app has requested legacy storage we might need to update
+     * {@link android.app.AppOpsManager#OP_LEGACY_STORAGE}. Hence force an update in
+     * {@link com.android.server.policy.PermissionPolicyService#synchronizePackagePermissionsAndAppOpsForUser(Context, String, int)}
+     *
+     * @param pkg The package for which the permissions are updated
+     * @param replace If the app is being replaced
+     * @param userIds All user IDs in the system, must be passed in because this method is locked
+     * @param updatedUserIds The ids of the users that already changed.
+     *
+     * @return The ids of the users that are changed
+     */
+    private @NonNull int[] checkIfLegacyStorageOpsNeedToBeUpdated(@NonNull AndroidPackage pkg,
+            boolean replace, @NonNull int[] userIds, @NonNull int[] updatedUserIds) {
+        if (replace && pkg.isRequestLegacyExternalStorage() && (
+                pkg.getRequestedPermissions().contains(READ_EXTERNAL_STORAGE)
+                        || pkg.getRequestedPermissions().contains(WRITE_EXTERNAL_STORAGE))) {
+            return userIds.clone();
+        }
+
+        return updatedUserIds;
+    }
+
+    /**
+     * Set the state of a implicit permission that is seen for the first time.
+     *
+     * @param origPs The permission state of the package before the split
+     * @param ps The new permission state
+     * @param pkg The package the permission belongs to
+     * @param userId The user ID
+     * @param updatedUserIds List of users for which the permission state has already been changed
+     *
+     * @return  List of users for which the permission state has been changed
+     */
+    @NonNull
+    @GuardedBy("mLock")
+    private int[] setInitialGrantForNewImplicitPermissionsLocked(
+            @NonNull UidPermissionState origPs, @NonNull UidPermissionState ps,
+            @NonNull AndroidPackage pkg, @NonNull ArraySet<String> newImplicitPermissions,
+            @UserIdInt int userId, @NonNull int[] updatedUserIds) {
+        String pkgName = pkg.getPackageName();
+        ArrayMap<String, ArraySet<String>> newToSplitPerms = new ArrayMap<>();
+
+        final List<PermissionManager.SplitPermissionInfo> permissionList =
+                getSplitPermissionInfos();
+        int numSplitPerms = permissionList.size();
+        for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+            PermissionManager.SplitPermissionInfo spi = permissionList.get(splitPermNum);
+
+            List<String> newPerms = spi.getNewPermissions();
+            int numNewPerms = newPerms.size();
+            for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
+                String newPerm = newPerms.get(newPermNum);
+
+                ArraySet<String> splitPerms = newToSplitPerms.get(newPerm);
+                if (splitPerms == null) {
+                    splitPerms = new ArraySet<>();
+                    newToSplitPerms.put(newPerm, splitPerms);
+                }
+
+                splitPerms.add(spi.getSplitPermission());
+            }
+        }
+
+        int numNewImplicitPerms = newImplicitPermissions.size();
+        for (int newImplicitPermNum = 0; newImplicitPermNum < numNewImplicitPerms;
+                newImplicitPermNum++) {
+            String newPerm = newImplicitPermissions.valueAt(newImplicitPermNum);
+            ArraySet<String> sourcePerms = newToSplitPerms.get(newPerm);
+
+            if (sourcePerms != null) {
+                Permission bp = mRegistry.getPermission(newPerm);
+                if (bp == null) {
+                    throw new IllegalStateException("Unknown new permission in split permission: "
+                            + newPerm);
+                }
+                if (bp.isRuntime()) {
+
+                    if (!newPerm.equals(Manifest.permission.ACTIVITY_RECOGNITION)) {
+                        ps.updatePermissionFlags(bp,
+                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+                                FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+                    }
+                    updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+
+                    if (!origPs.hasPermissionState(sourcePerms)) {
+                        boolean inheritsFromInstallPerm = false;
+                        for (int sourcePermNum = 0; sourcePermNum < sourcePerms.size();
+                                sourcePermNum++) {
+                            final String sourcePerm = sourcePerms.valueAt(sourcePermNum);
+                            Permission sourceBp = mRegistry.getPermission(sourcePerm);
+                            if (sourceBp == null) {
+                                throw new IllegalStateException("Unknown source permission in split"
+                                        + " permission: " + sourcePerm);
+                            }
+                            if (!sourceBp.isRuntime()) {
+                                inheritsFromInstallPerm = true;
+                                break;
+                            }
+                        }
+
+                        if (!inheritsFromInstallPerm) {
+                            // Both permissions are new so nothing to inherit.
+                            if (DEBUG_PERMISSIONS) {
+                                Slog.i(TAG, newPerm + " does not inherit from " + sourcePerms
+                                        + " for " + pkgName + " as split permission is also new");
+                            }
+                            continue;
+                        }
+                    }
+
+                    // Inherit from new install or existing runtime permissions
+                    inheritPermissionStateToNewImplicitPermissionLocked(sourcePerms, newPerm, ps,
+                            pkg);
+                }
+            } else if (IMPLICIT_GRANTED_PERMISSIONS.contains(newPerm)
+                    && !origPs.hasPermissionState(newPerm)) {
+                Permission bp = mRegistry.getPermission(newPerm);
+                if (bp == null) {
+                    throw new IllegalStateException("Unknown new permission " + newPerm);
+                }
+                if ((ps.getPermissionState(newPerm).getFlags()
+                        & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                    // No need to grant if review is required
+                    continue;
+                }
+                updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId);
+                ps.updatePermissionFlags(bp,
+                        FLAG_PERMISSION_REVOKE_WHEN_REQUESTED,
+                        FLAG_PERMISSION_REVOKE_WHEN_REQUESTED);
+                ps.grantPermission(bp);
+            }
+        }
+
+        return updatedUserIds;
+    }
+
+    @NonNull
+    @Override
+    public List<SplitPermissionInfoParcelable> getSplitPermissions() {
+        return PermissionManager.splitPermissionInfoListToParcelableList(getSplitPermissionInfos());
+    }
+
+    @NonNull
+    private List<PermissionManager.SplitPermissionInfo> getSplitPermissionInfos() {
+        return SystemConfig.getInstance().getSplitPermissions();
+    }
+
+    private static boolean isCompatPlatformPermissionForPackage(String perm, AndroidPackage pkg) {
+        boolean allowed = false;
+        for (int i = 0, size = CompatibilityPermissionInfo.COMPAT_PERMS.length; i < size; i++) {
+            final CompatibilityPermissionInfo info = CompatibilityPermissionInfo.COMPAT_PERMS[i];
+            if (info.getName().equals(perm)
+                    && pkg.getTargetSdkVersion() < info.getSdkVersion()) {
+                allowed = true;
+                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+                        + pkg.getPackageName());
+                break;
+            }
+        }
+        return allowed;
+    }
+
+    private boolean checkPrivilegedPermissionAllowlist(@NonNull AndroidPackage pkg,
+            @NonNull PackageStateInternal packageSetting, @NonNull Permission permission) {
+        if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
+            return true;
+        }
+        final String packageName = pkg.getPackageName();
+        if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
+            return true;
+        }
+        if (!pkg.isPrivileged()) {
+            return true;
+        }
+        if (!mPrivilegedPermissionAllowlistSourcePackageNames
+                .contains(permission.getPackageName())) {
+            return true;
+        }
+        final String permissionName = permission.getName();
+        final ApexManager apexManager = ApexManager.getInstance();
+        final String containingApexPackageName =
+                apexManager.getActiveApexPackageNameContainingPackage(packageName);
+        if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
+                containingApexPackageName)) {
+            return true;
+        }
+        if (isInSystemConfigPrivAppDenyPermissions(pkg, permissionName,
+                containingApexPackageName)) {
+            return false;
+        }
+        // Updated system apps do not need to be allowlisted
+        if (packageSetting.getTransientState().isUpdatedSystemApp()) {
+            // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission
+            // can be granted, because an updated system app may be in a shared UID, and in case a
+            // new privileged permission is requested by the updated system app but not the factory
+            // app, although this app and permission combination isn't in the allowlist and can't
+            // get the permission this way, other apps in the shared UID may still get it. A proper
+            // fix for this would be to perform the reconciliation by UID, but for now let's keep
+            // the old workaround working, which is to keep granted privileged permissions still
+            // granted.
+            return true;
+        }
+        // Only enforce the allowlist on boot
+        if (!mSystemReady) {
+            final boolean isInUpdatedApex = containingApexPackageName != null
+                    && !apexManager.isFactory(apexManager.getPackageInfo(containingApexPackageName,
+                    MATCH_ACTIVE_PACKAGE));
+            // Apps that are in updated apexs' do not need to be allowlisted
+            if (!isInUpdatedApex) {
+                Slog.w(TAG, "Privileged permission " + permissionName + " for package "
+                        + packageName + " (" + pkg.getPath()
+                        + ") not in privapp-permissions allowlist");
+                if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
+                    synchronized (mLock) {
+                        if (mPrivappPermissionsViolations == null) {
+                            mPrivappPermissionsViolations = new ArraySet<>();
+                        }
+                        mPrivappPermissionsViolations.add(packageName + " (" + pkg.getPath() + "): "
+                                + permissionName);
+                    }
+                }
+            }
+        }
+        return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE;
+    }
+
+    private boolean isInSystemConfigPrivAppPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission, String containingApexPackageName) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
+        } else if (containingApexPackageName != null) {
+            final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
+                    pkg.getPackageName());
+            final Set<String> apexPermissions = systemConfig.getApexPrivAppPermissions(
+                    containingApexPackageName, pkg.getPackageName());
+            if (privAppPermissions != null) {
+                // TODO(andreionea): Remove check as soon as all apk-in-apex
+                // permission allowlists are migrated.
+                Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX,"
+                        + " but has permission allowlist on the system image. Please bundle the"
+                        + " allowlist in the " + containingApexPackageName + " APEX instead.");
+                if (apexPermissions != null) {
+                    permissions = new ArraySet<>(privAppPermissions);
+                    permissions.addAll(apexPermissions);
+                } else {
+                    permissions = privAppPermissions;
+                }
+            } else {
+                permissions = apexPermissions;
+            }
+        } else {
+            permissions = systemConfig.getPrivAppPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean isInSystemConfigPrivAppDenyPermissions(@NonNull AndroidPackage pkg,
+            @NonNull String permission, String containingApexPackageName) {
+        final SystemConfig systemConfig = SystemConfig.getInstance();
+        final Set<String> permissions;
+        if (pkg.isVendor()) {
+            permissions = systemConfig.getVendorPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isProduct()) {
+            permissions = systemConfig.getProductPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (pkg.isSystemExt()) {
+            permissions = systemConfig.getSystemExtPrivAppDenyPermissions(pkg.getPackageName());
+        } else if (containingApexPackageName != null) {
+            permissions = systemConfig.getApexPrivAppDenyPermissions(containingApexPackageName,
+                    pkg.getPackageName());
+        } else {
+            permissions = systemConfig.getPrivAppDenyPermissions(pkg.getPackageName());
+        }
+        return CollectionUtils.contains(permissions, permission);
+    }
+
+    private boolean shouldGrantPermissionBySignature(@NonNull AndroidPackage pkg,
+            @NonNull Permission bp) {
+        // expect single system package
+        String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM));
+        final AndroidPackage systemPackage =
+                mPackageManagerInt.getPackage(systemPackageName);
+        // 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
+        final SigningDetails sourceSigningDetails =
+                getSourcePackageSigningDetails(bp);
+        return sourceSigningDetails.hasCommonSignerWithCapability(
+                        pkg.getSigningDetails(),
+                        SigningDetails.CertCapabilities.PERMISSION)
+                || pkg.getSigningDetails().hasAncestorOrSelf(systemPackage.getSigningDetails())
+                || systemPackage.getSigningDetails().checkCapability(
+                        pkg.getSigningDetails(),
+                        SigningDetails.CertCapabilities.PERMISSION);
+    }
+
+    private boolean shouldGrantPermissionByProtectionFlags(@NonNull AndroidPackage pkg,
+            @NonNull PackageStateInternal pkgSetting, @NonNull Permission bp,
+            @NonNull ArraySet<String> shouldGrantPrivilegedPermissionIfWasGranted) {
+        boolean allowed = false;
+        final boolean isPrivilegedPermission = bp.isPrivileged();
+        final boolean isOemPermission = bp.isOem();
+        if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
+            final String permissionName = bp.getName();
+            // For updated system applications, a privileged/oem permission
+            // is granted only if it had been defined by the original application.
+            if (pkgSetting.getTransientState().isUpdatedSystemApp()) {
+                final PackageSetting disabledPs = mPackageManagerInt
+                        .getDisabledSystemPackage(pkg.getPackageName());
+                final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg();
+                if (disabledPkg != null
+                        && ((isPrivilegedPermission && disabledPkg.isPrivileged())
+                        || (isOemPermission && canGrantOemPermission(disabledPkg,
+                                permissionName)))) {
+                    if (disabledPkg.getRequestedPermissions().contains(permissionName)) {
+                        allowed = true;
+                    } else {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        shouldGrantPrivilegedPermissionIfWasGranted.add(permissionName);
+                    }
+                }
+            } else {
+                allowed = (isPrivilegedPermission && pkg.isPrivileged())
+                        || (isOemPermission && canGrantOemPermission(pkg, permissionName));
+            }
+            // 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 (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
+                Slog.w(TAG, "Permission " + permissionName
+                        + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
+                        + " because it isn't a 'vendorPrivileged' permission.");
+                allowed = false;
+            }
+        }
+        if (!allowed && bp.isPre23() && pkg.getTargetSdkVersion() < 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.
+            allowed = true;
+        }
+        // TODO (moltmann): The installer now shares the platforms signature. Hence it does not
+        //                  need a separate flag anymore. Hence we need to check which
+        //                  permissions are needed by the permission controller
+        if (!allowed && bp.isInstaller()
+                && (ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
+                pkg.getPackageName()) || ArrayUtils.contains(
+                        mPackageManagerInt.getKnownPackageNames(
+                                PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
+                UserHandle.USER_SYSTEM), pkg.getPackageName()))) {
+            // If this permission is to be granted to the system installer and
+            // this app is an installer, then it gets the permission.
+            allowed = true;
+        }
+        if (!allowed && bp.isVerifier()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
+                pkg.getPackageName())) {
+            // If this permission is to be granted to the system verifier and
+            // this app is a verifier, then it gets the permission.
+            allowed = true;
+        }
+        if (!allowed && bp.isPreInstalled()
+                && pkg.isSystem()) {
+            // Any pre-installed system app is allowed to get this permission.
+            allowed = true;
+        }
+        if (!allowed && bp.isKnownSigner()) {
+            // 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.
+            allowed = pkg.getSigningDetails().hasAncestorOrSelfWithDigest(bp.getKnownCerts());
+        }
+        // Deferred to be checked under permission data lock inside restorePermissionState().
+        //if (!allowed && bp.isDevelopment()) {
+        //    // For development permissions, a development permission
+        //    // is granted only if it was already granted.
+        //    allowed = origPermissions.isPermissionGranted(permissionName);
+        //}
+        if (!allowed && bp.isSetup()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
+                pkg.getPackageName())) {
+            // If this permission is to be granted to the system setup wizard and
+            // this app is a setup wizard, then it gets the permission.
+            allowed = true;
+        }
+        if (!allowed && bp.isSystemTextClassifier()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
+                UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+            // Special permissions for the system default text classifier.
+            allowed = true;
+        }
+        if (!allowed && bp.isConfigurator()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_CONFIGURATOR,
+                UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+            // Special permissions for the device configurator.
+            allowed = true;
+        }
+        if (!allowed && bp.isIncidentReportApprover()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
+                UserHandle.USER_SYSTEM), pkg.getPackageName())) {
+            // 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.
+            allowed = true;
+        }
+        if (!allowed && bp.isAppPredictor()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
+                pkg.getPackageName())) {
+            // Special permissions for the system app predictor.
+            allowed = true;
+        }
+        if (!allowed && bp.isCompanion()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                    PackageManagerInternal.PACKAGE_COMPANION, UserHandle.USER_SYSTEM),
+                pkg.getPackageName())) {
+            // Special permissions for the system companion device manager.
+            allowed = true;
+        }
+        if (!allowed && bp.isRetailDemo()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                        PackageManagerInternal.PACKAGE_RETAIL_DEMO, UserHandle.USER_SYSTEM),
+                pkg.getPackageName()) && isProfileOwner(pkg.getUid())) {
+            // Special permission granted only to the OEM specified retail demo app
+            allowed = true;
+        }
+        if (!allowed && bp.isRecents()
+                && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+                PackageManagerInternal.PACKAGE_RECENTS, UserHandle.USER_SYSTEM),
+                pkg.getPackageName())) {
+            // Special permission for the recents app.
+            allowed = true;
+        }
+        return allowed;
+    }
+
+    @NonNull
+    private SigningDetails getSourcePackageSigningDetails(
+            @NonNull Permission bp) {
+        final PackageStateInternal ps = getSourcePackageSetting(bp);
+        if (ps == null) {
+            return SigningDetails.UNKNOWN;
+        }
+        return ps.getSigningDetails();
+    }
+
+    @Nullable
+    private PackageStateInternal getSourcePackageSetting(@NonNull Permission bp) {
+        final String sourcePackageName = bp.getPackageName();
+        return mPackageManagerInt.getPackageStateInternal(sourcePackageName);
+    }
+
+    private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
+        if (!pkg.isOem()) {
+            return false;
+        }
+        // all oem permissions must explicitly be granted or denied
+        final Boolean granted =
+                SystemConfig.getInstance().getOemPermissions(pkg.getPackageName()).get(permission);
+        if (granted == null) {
+            throw new IllegalStateException("OEM permission" + permission + " requested by package "
+                    + pkg.getPackageName() + " must be explicitly declared granted or not");
+        }
+        return Boolean.TRUE == granted;
+    }
+
+    private static boolean isProfileOwner(int uid) {
+        DevicePolicyManagerInternal dpmInternal =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+        //TODO(b/169395065) Figure out if this flow makes sense in Device Owner mode.
+        if (dpmInternal != null) {
+            return dpmInternal.isActiveProfileOwner(uid) || dpmInternal.isActiveDeviceOwner(uid);
+        }
+        return false;
+    }
+
+    private boolean isPermissionsReviewRequiredInternal(@NonNull String packageName,
+            @UserIdInt int userId) {
+        final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return false;
+        }
+
+        // Permission review applies only to apps not supporting the new permission model.
+        if (pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) {
+            return false;
+        }
+
+        // Legacy apps have the permission and get user consent on launch.
+        synchronized (mLock) {
+            final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName() + " and user "
+                        + userId);
+                return false;
+            }
+            return uidState.isPermissionsReviewRequired();
+        }
+    }
+
+    private void grantRequestedRuntimePermissionsInternal(@NonNull AndroidPackage pkg,
+            @Nullable List<String> permissions, int userId) {
+        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+        final int compatFlags = PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+                | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
+
+        final boolean supportsRuntimePermissions = pkg.getTargetSdkVersion()
+                >= Build.VERSION_CODES.M;
+
+        final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.getPackageName(), userId);
+
+        final int myUid = Process.myUid();
+
+        for (String permission : pkg.getRequestedPermissions()) {
+            final boolean shouldGrantPermission;
+            synchronized (mLock) {
+                final Permission bp = mRegistry.getPermission(permission);
+                shouldGrantPermission = bp != null && (bp.isRuntime() || bp.isDevelopment())
+                        && (!instantApp || bp.isInstant())
+                        && (supportsRuntimePermissions || !bp.isRuntimeOnly())
+                        && (permissions == null || permissions.contains(permission));
+            }
+            if (shouldGrantPermission) {
+                final int flags = getPermissionFlagsInternal(pkg.getPackageName(), permission,
+                        myUid, userId);
+                if (supportsRuntimePermissions) {
+                    // Installer cannot change immutable permissions.
+                    if ((flags & immutableFlags) == 0) {
+                        grantRuntimePermissionInternal(pkg.getPackageName(), permission, false,
+                                myUid, userId, mDefaultPermissionCallback);
+                    }
+                } else {
+                    // In permission review mode we clear the review flag and the revoked compat
+                    // flag when we are asked to install the app with all permissions granted.
+                    if ((flags & compatFlags) != 0) {
+                        updatePermissionFlagsInternal(pkg.getPackageName(), permission, compatFlags,
+                                0, myUid, userId, false, mDefaultPermissionCallback);
+                    }
+                }
+            }
+        }
+    }
+
+    private void setAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
+            @Nullable List<String> permissions,
+            @PackageManager.PermissionWhitelistFlags int allowlistFlags,
+            @UserIdInt int userId) {
+        ArraySet<String> oldGrantedRestrictedPermissions = null;
+        boolean updatePermissions = false;
+        final int permissionCount = pkg.getRequestedPermissions().size();
+        final int myUid = Process.myUid();
+
+        for (int j = 0; j < permissionCount; j++) {
+            final String permissionName = pkg.getRequestedPermissions().get(j);
+
+            final boolean isGranted;
+            synchronized (mLock) {
+                final Permission bp = mRegistry.getPermission(permissionName);
+                if (bp == null || !bp.isHardOrSoftRestricted()) {
+                    continue;
+                }
+
+                final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+                if (uidState == null) {
+                    Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+                            + " and user " + userId);
+                    continue;
+                }
+                isGranted = uidState.isPermissionGranted(permissionName);
+            }
+
+            if (isGranted) {
+                if (oldGrantedRestrictedPermissions == null) {
+                    oldGrantedRestrictedPermissions = new ArraySet<>();
+                }
+                oldGrantedRestrictedPermissions.add(permissionName);
+            }
+
+            final int oldFlags = getPermissionFlagsInternal(pkg.getPackageName(), permissionName,
+                    myUid, userId);
+
+            int newFlags = oldFlags;
+            int mask = 0;
+            int allowlistFlagsCopy = allowlistFlags;
+            while (allowlistFlagsCopy != 0) {
+                final int flag = 1 << Integer.numberOfTrailingZeros(allowlistFlagsCopy);
+                allowlistFlagsCopy &= ~flag;
+                switch (flag) {
+                    case FLAG_PERMISSION_WHITELIST_SYSTEM: {
+                        mask |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                        if (permissions != null && permissions.contains(permissionName)) {
+                            newFlags |= FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                        } else {
+                            newFlags &= ~FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
+                        }
+                    }
+                    break;
+                    case FLAG_PERMISSION_WHITELIST_UPGRADE: {
+                        mask |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                        if (permissions != null && permissions.contains(permissionName)) {
+                            newFlags |= FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                        } else {
+                            newFlags &= ~FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+                        }
+                    }
+                    break;
+                    case FLAG_PERMISSION_WHITELIST_INSTALLER: {
+                        mask |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                        if (permissions != null && permissions.contains(permissionName)) {
+                            newFlags |= FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                        } else {
+                            newFlags &= ~FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
+                        }
+                    }
+                    break;
+                }
+            }
+
+            if (oldFlags == newFlags) {
+                continue;
+            }
+
+            updatePermissions = true;
+
+            final boolean wasAllowlisted = (oldFlags
+                    & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
+            final boolean isAllowlisted = (newFlags
+                    & (PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)) != 0;
+
+            // If the permission is policy fixed as granted but it is no longer
+            // on any of the allowlists we need to clear the policy fixed flag
+            // as allowlisting trumps policy i.e. policy cannot grant a non
+            // grantable permission.
+            if ((oldFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+                if (!isAllowlisted && isGranted) {
+                    mask |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                    newFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                }
+            }
+
+            // If we are allowlisting an app that does not support runtime permissions
+            // we need to make sure it goes through the permission review UI at launch.
+            if (pkg.getTargetSdkVersion() < Build.VERSION_CODES.M
+                    && !wasAllowlisted && isAllowlisted) {
+                mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+                newFlags |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+            }
+
+            updatePermissionFlagsInternal(pkg.getPackageName(), permissionName, mask, newFlags,
+                    myUid, userId, false, null /*callback*/);
+        }
+
+        if (updatePermissions) {
+            // Update permission of this app to take into account the new allowlist state.
+            restorePermissionState(pkg, false, pkg.getPackageName(), mDefaultPermissionCallback,
+                    userId);
+
+            // If this resulted in losing a permission we need to kill the app.
+            if (oldGrantedRestrictedPermissions == null) {
+                return;
+            }
+
+            final int oldGrantedCount = oldGrantedRestrictedPermissions.size();
+            for (int j = 0; j < oldGrantedCount; j++) {
+                final String permissionName = oldGrantedRestrictedPermissions.valueAt(j);
+                // Sometimes we create a new permission state instance during update.
+                final boolean isGranted;
+                synchronized (mLock) {
+                    final UidPermissionState uidState = getUidStateLocked(pkg, userId);
+                    if (uidState == null) {
+                        Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+                                + " and user " + userId);
+                        continue;
+                    }
+                    isGranted = uidState.isPermissionGranted(permissionName);
+                }
+                if (!isGranted) {
+                    mDefaultPermissionCallback.onPermissionRevoked(pkg.getUid(), userId, null);
+                    break;
+                }
+            }
+        }
+    }
+
+    private void revokeSharedUserPermissionsForLeavingPackageInternal(
+            @Nullable AndroidPackage pkg, int appId, @NonNull List<AndroidPackage> sharedUserPkgs,
+            @UserIdInt int userId) {
+        if (pkg == null) {
+            Slog.i(TAG, "Trying to update info for null package. Just ignoring");
+            return;
+        }
+
+        // No shared user packages
+        if (sharedUserPkgs.isEmpty()) {
+            return;
+        }
+
+        PackageSetting disabledPs = mPackageManagerInt.getDisabledSystemPackage(
+                pkg.getPackageName());
+        boolean isShadowingSystemPkg = disabledPs != null && disabledPs.getAppId() == pkg.getUid();
+
+        boolean shouldKillUid = false;
+        // Update permissions
+        for (String eachPerm : pkg.getRequestedPermissions()) {
+            // Check if another package in the shared user needs the permission.
+            boolean used = false;
+            for (AndroidPackage sharedUserpkg : sharedUserPkgs) {
+                if (sharedUserpkg != null
+                        && !sharedUserpkg.getPackageName().equals(pkg.getPackageName())
+                        && sharedUserpkg.getRequestedPermissions().contains(eachPerm)) {
+                    used = true;
+                    break;
+                }
+            }
+            if (used) {
+                continue;
+            }
+
+            // If the package is shadowing a disabled system package,
+            // do not drop permissions that the shadowed package requests.
+            if (isShadowingSystemPkg
+                    && disabledPs.getPkg().getRequestedPermissions().contains(eachPerm)) {
+                continue;
+            }
+
+            synchronized (mLock) {
+                UidPermissionState uidState = getUidStateLocked(appId, userId);
+                if (uidState == null) {
+                    Slog.e(TAG, "Missing permissions state for " + pkg.getPackageName()
+                            + " and user " + userId);
+                    continue;
+                }
+
+                Permission bp = mRegistry.getPermission(eachPerm);
+                if (bp == null) {
+                    continue;
+                }
+
+                // TODO(zhanghai): Why are we only killing the UID when GIDs changed, instead of any
+                //  permission change?
+                if (uidState.removePermissionState(bp.getName()) && bp.hasGids()) {
+                    shouldKillUid = true;
+                }
+            }
+        }
+
+        // If gids changed, kill all affected packages.
+        if (shouldKillUid) {
+            mHandler.post(() -> {
+                // This has to happen with no lock held.
+                killUid(appId, UserHandle.USER_ALL, KILL_APP_REASON_GIDS_CHANGED);
+            });
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean revokeUnusedSharedUserPermissionsLocked(
+            List<AndroidPackage> pkgList, UidPermissionState uidState) {
+        // Collect all used permissions in the UID
+        final ArraySet<String> usedPermissions = new ArraySet<>();
+        if (pkgList == null || pkgList.size() == 0) {
+            return false;
+        }
+        for (AndroidPackage pkg : pkgList) {
+            if (pkg.getRequestedPermissions().isEmpty()) {
+                continue;
+            }
+            final int requestedPermCount = pkg.getRequestedPermissions().size();
+            for (int j = 0; j < requestedPermCount; j++) {
+                String permission = pkg.getRequestedPermissions().get(j);
+                Permission bp = mRegistry.getPermission(permission);
+                if (bp != null) {
+                    usedPermissions.add(permission);
+                }
+            }
+        }
+
+        boolean runtimePermissionChanged = false;
+
+        // Prune permissions
+        final List<PermissionState> permissionStates = uidState.getPermissionStates();
+        final int permissionStatesSize = permissionStates.size();
+        for (int i = permissionStatesSize - 1; i >= 0; i--) {
+            PermissionState permissionState = permissionStates.get(i);
+            if (!usedPermissions.contains(permissionState.getName())) {
+                Permission bp = mRegistry.getPermission(permissionState.getName());
+                if (bp != null) {
+                    if (uidState.removePermissionState(bp.getName()) && bp.isRuntime()) {
+                        runtimePermissionChanged = true;
+                    }
+                }
+            }
+        }
+
+        return runtimePermissionChanged;
+    }
+
+    /**
+     * Update permissions when a package changed.
+     *
+     * <p><ol>
+     *     <li>Reconsider the ownership of permission</li>
+     *     <li>Update the state (grant, flags) of the permissions</li>
+     * </ol>
+     *
+     * @param packageName The package that is updated
+     * @param pkg The package that is updated, or {@code null} if package is deleted
+     */
+    private void updatePermissions(@NonNull String packageName, @Nullable AndroidPackage pkg) {
+        // If the package is being deleted, update the permissions of all the apps
+        final int flags =
+                (pkg == null ? UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG
+                        : UPDATE_PERMISSIONS_REPLACE_PKG);
+        updatePermissions(
+                packageName, pkg, getVolumeUuidForPackage(pkg), flags, mDefaultPermissionCallback);
+    }
+
+    /**
+     * Update all permissions for all apps.
+     *
+     * <p><ol>
+     *     <li>Reconsider the ownership of permission</li>
+     *     <li>Update the state (grant, flags) of the permissions</li>
+     * </ol>
+     *
+     * @param volumeUuid The volume UUID of the packages to be updated
+     * @param fingerprintChanged whether the current build fingerprint is different from what it was
+     *                           when this volume was last mounted
+     */
+    private void updateAllPermissions(@NonNull String volumeUuid, boolean fingerprintChanged) {
+        PackageManager.corkPackageInfoCache();  // Prevent invalidation storm
+        try {
+            final int flags = UPDATE_PERMISSIONS_ALL |
+                    (fingerprintChanged
+                            ? UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL
+                            : 0);
+            updatePermissions(null, null, volumeUuid, flags, mDefaultPermissionCallback);
+        } finally {
+            PackageManager.uncorkPackageInfoCache();
+        }
+    }
+
+    /**
+     * Update all packages on the volume, <u>beside</u> the changing package. If the changing
+     * package is set too, all packages are updated.
+     */
+    private static final int UPDATE_PERMISSIONS_ALL = 1 << 0;
+    /** The changing package is replaced. Requires the changing package to be set */
+    private static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1 << 1;
+    /**
+     * Schedule all packages <u>beside</u> the changing package for replacement. Requires
+     * UPDATE_PERMISSIONS_ALL to be set
+     */
+    private static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1 << 2;
+
+    @IntDef(flag = true, prefix = { "UPDATE_PERMISSIONS_" }, value = {
+            UPDATE_PERMISSIONS_ALL, UPDATE_PERMISSIONS_REPLACE_PKG,
+            UPDATE_PERMISSIONS_REPLACE_ALL })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface UpdatePermissionFlags {}
+
+    /**
+     * Update permissions when packages changed.
+     *
+     * <p><ol>
+     *     <li>Reconsider the ownership of permission</li>
+     *     <li>Update the state (grant, flags) of the permissions</li>
+     * </ol>
+     *
+     * <p>Meaning of combination of package parameters:
+     * <table>
+     *     <tr><th></th><th>changingPkgName != null</th><th>changingPkgName == null</th></tr>
+     *     <tr><th>changingPkg != null</th><td>package is updated</td><td>invalid</td></tr>
+     *     <tr><th>changingPkg == null</th><td>package is deleted</td><td>all packages are
+     *                                                                    updated</td></tr>
+     * </table>
+     *
+     * @param changingPkgName The package that is updated, or {@code null} if all packages should be
+     *                    updated
+     * @param changingPkg The package that is updated, or {@code null} if all packages should be
+     *                    updated or package is deleted
+     * @param replaceVolumeUuid The volume of the packages to be updated are on, {@code null} for
+     *                          all volumes
+     * @param flags Control permission for which apps should be updated
+     * @param callback Callback to call after permission changes
+     */
+    private void updatePermissions(final @Nullable String changingPkgName,
+            final @Nullable AndroidPackage changingPkg,
+            final @Nullable String replaceVolumeUuid,
+            @UpdatePermissionFlags int flags,
+            final @Nullable PermissionCallback callback) {
+        // TODO: Most of the methods exposing BasePermission internals [source package name,
+        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+        // have package settings, we should make note of it elsewhere [map between
+        // source package name and BasePermission] and cycle through that here. Then we
+        // define a single method on BasePermission that takes a PackageSetting, changing
+        // package name and a package.
+        // NOTE: With this approach, we also don't need to tree trees differently than
+        // normal permissions. Today, we need two separate loops because these BasePermission
+        // objects are stored separately.
+        // Make sure there are no dangling permission trees.
+        boolean permissionTreesSourcePackageChanged = updatePermissionTreeSourcePackage(
+                changingPkgName, changingPkg);
+        // Make sure all dynamic permissions have been assigned to a package,
+        // and make sure there are no dangling permissions.
+        boolean permissionSourcePackageChanged = updatePermissionSourcePackage(changingPkgName,
+                callback);
+
+        if (permissionTreesSourcePackageChanged | permissionSourcePackageChanged) {
+            // Permission ownership has changed. This e.g. changes which packages can get signature
+            // permissions
+            Slog.i(TAG, "Permission ownership changed. Updating all permissions.");
+            flags |= UPDATE_PERMISSIONS_ALL;
+        }
+
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "restorePermissionState");
+        // Now update the permissions for all packages.
+        if ((flags & UPDATE_PERMISSIONS_ALL) != 0) {
+            final boolean replaceAll = ((flags & UPDATE_PERMISSIONS_REPLACE_ALL) != 0);
+            mPackageManagerInt.forEachPackage((AndroidPackage pkg) -> {
+                if (pkg == changingPkg) {
+                    return;
+                }
+                // Only replace for packages on requested volume
+                final String volumeUuid = getVolumeUuidForPackage(pkg);
+                final boolean replace = replaceAll && Objects.equals(replaceVolumeUuid, volumeUuid);
+                restorePermissionState(pkg, replace, changingPkgName, callback,
+                        UserHandle.USER_ALL);
+            });
+        }
+
+        if (changingPkg != null) {
+            // Only replace for packages on requested volume
+            final String volumeUuid = getVolumeUuidForPackage(changingPkg);
+            final boolean replace = ((flags & UPDATE_PERMISSIONS_REPLACE_PKG) != 0)
+                    && Objects.equals(replaceVolumeUuid, volumeUuid);
+            restorePermissionState(changingPkg, replace, changingPkgName, callback,
+                    UserHandle.USER_ALL);
+        }
+        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+    }
+
+    /**
+     * Update which app declares a permission.
+     *
+     * @param packageName The package that is updated, or {@code null} if all packages should be
+     *                    updated
+     *
+     * @return {@code true} if a permission source package might have changed
+     */
+    private boolean updatePermissionSourcePackage(@Nullable String packageName,
+            final @Nullable PermissionCallback callback) {
+        // Always need update if packageName is null
+        if (packageName == null) {
+            return true;
+        }
+
+        boolean changed = false;
+        Set<Permission> needsUpdate = null;
+        synchronized (mLock) {
+            for (final Permission bp : mRegistry.getPermissions()) {
+                if (bp.isDynamic()) {
+                    bp.updateDynamicPermission(mRegistry.getPermissionTrees());
+                }
+                if (!packageName.equals(bp.getPackageName())) {
+                    // Not checking sourcePackageSetting because it can be null when
+                    // the permission source package is the target package and the target package is
+                    // being uninstalled,
+                    continue;
+                }
+                // The target package is the source of the current permission
+                // Set to changed for either install or uninstall
+                changed = true;
+                if (needsUpdate == null) {
+                    needsUpdate = new ArraySet<>();
+                }
+                needsUpdate.add(bp);
+            }
+        }
+        if (needsUpdate != null) {
+            final AndroidPackage pkg = mPackageManagerInt.getPackage(packageName);
+            for (final Permission bp : needsUpdate) {
+                // If the target package is being uninstalled, we need to revoke this permission
+                // From all other packages
+                if (pkg == null || !hasPermission(pkg, bp.getName())) {
+                    if (!isPermissionDeclaredByDisabledSystemPkg(bp)) {
+                        Slog.i(TAG, "Removing permission " + bp.getName()
+                                + " that used to be declared by " + bp.getPackageName());
+                        if (bp.isRuntime()) {
+                            final int[] userIds = mUserManagerInt.getUserIds();
+                            final int numUserIds = userIds.length;
+                            for (int userIdNum = 0; userIdNum < numUserIds; userIdNum++) {
+                                final int userId = userIds[userIdNum];
+                                mPackageManagerInt.forEachPackage((AndroidPackage p) ->
+                                        revokePermissionFromPackageForUser(p.getPackageName(),
+                                                bp.getName(), true, userId, callback));
+                            }
+                        } else {
+                            mPackageManagerInt.forEachPackage(p -> {
+                                final int[] userIds = mUserManagerInt.getUserIds();
+                                synchronized (mLock) {
+                                    for (final int userId : userIds) {
+                                        final UidPermissionState uidState = getUidStateLocked(p,
+                                                userId);
+                                        if (uidState == null) {
+                                            Slog.e(TAG, "Missing permissions state for "
+                                                    + p.getPackageName() + " and user " + userId);
+                                            continue;
+                                        }
+                                        uidState.removePermissionState(bp.getName());
+                                    }
+                                }
+                            });
+                        }
+                    }
+                    synchronized (mLock) {
+                        mRegistry.removePermission(bp.getName());
+                    }
+                    continue;
+                }
+                final AndroidPackage sourcePkg =
+                        mPackageManagerInt.getPackage(bp.getPackageName());
+                final PackageStateInternal sourcePs =
+                        mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
+                synchronized (mLock) {
+                    if (sourcePkg != null && sourcePs != null) {
+                        continue;
+                    }
+                    Slog.w(TAG, "Removing dangling permission: " + bp.getName()
+                            + " from package " + bp.getPackageName());
+                    mRegistry.removePermission(bp.getName());
+                }
+            }
+        }
+        return changed;
+    }
+
+    private boolean isPermissionDeclaredByDisabledSystemPkg(@NonNull Permission permission) {
+        final PackageSetting disabledSourcePs = mPackageManagerInt.getDisabledSystemPackage(
+                    permission.getPackageName());
+        if (disabledSourcePs != null && disabledSourcePs.getPkg() != null) {
+            final String permissionName = permission.getName();
+            final List<ParsedPermission> sourcePerms = disabledSourcePs.getPkg().getPermissions();
+            for (ParsedPermission sourcePerm : sourcePerms) {
+                if (TextUtils.equals(permissionName, sourcePerm.getName())
+                        && permission.getProtectionLevel() == sourcePerm.getProtectionLevel()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Revoke a runtime permission from a package for a given user ID.
+     */
+    private void revokePermissionFromPackageForUser(@NonNull String pName,
+            @NonNull String permissionName, boolean overridePolicy, int userId,
+            @Nullable PermissionCallback callback) {
+        final ApplicationInfo appInfo =
+                mPackageManagerInt.getApplicationInfo(pName, 0,
+                        Process.SYSTEM_UID, UserHandle.USER_SYSTEM);
+        if (appInfo != null
+                && appInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+            return;
+        }
+
+        if (checkPermission(pName, permissionName, userId)
+                == PackageManager.PERMISSION_GRANTED) {
+            try {
+                revokeRuntimePermissionInternal(
+                        pName, permissionName,
+                        overridePolicy,
+                        Process.SYSTEM_UID,
+                        userId,
+                        null, callback);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG,
+                        "Failed to revoke "
+                                + permissionName
+                                + " from "
+                                + pName,
+                        e);
+            }
+        }
+    }
+
+    /**
+     * Update which app owns a permission trees.
+     *
+     * <p>Possible parameter combinations
+     * <table>
+     *     <tr><th></th><th>packageName != null</th><th>packageName == null</th></tr>
+     *     <tr><th>pkg != null</th><td>package is updated</td><td>invalid</td></tr>
+     *     <tr><th>pkg == null</th><td>package is deleted</td><td>all packages are updated</td></tr>
+     * </table>
+     *
+     * @param packageName The package that is updated, or {@code null} if all packages should be
+     *                    updated
+     * @param pkg The package that is updated, or {@code null} if all packages should be updated or
+     *            package is deleted
+     *
+     * @return {@code true} if a permission tree ownership might have changed
+     */
+    private boolean updatePermissionTreeSourcePackage(@Nullable String packageName,
+            @Nullable AndroidPackage pkg) {
+        // Always need update if packageName is null
+        if (packageName == null) {
+            return true;
+        }
+        boolean changed = false;
+
+        Set<Permission> needsUpdate = null;
+        synchronized (mLock) {
+            final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator();
+            while (it.hasNext()) {
+                final Permission bp = it.next();
+                if (!packageName.equals(bp.getPackageName())) {
+                    // Not checking sourcePackageSetting because it can be null when
+                    // the permission source package is the target package and the target package is
+                    // being uninstalled,
+                    continue;
+                }
+                // The target package is the source of the current permission tree
+                // Set to changed for either install or uninstall
+                changed = true;
+                if (pkg == null || !hasPermission(pkg, bp.getName())) {
+                    Slog.i(TAG, "Removing permission tree " + bp.getName()
+                            + " that used to be declared by " + bp.getPackageName());
+                    it.remove();
+                }
+                if (needsUpdate == null) {
+                    needsUpdate = new ArraySet<>();
+                }
+                needsUpdate.add(bp);
+            }
+        }
+        if (needsUpdate != null) {
+            for (final Permission bp : needsUpdate) {
+                final AndroidPackage sourcePkg =
+                        mPackageManagerInt.getPackage(bp.getPackageName());
+                final PackageStateInternal sourcePs =
+                        mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
+                synchronized (mLock) {
+                    if (sourcePkg != null && sourcePs != null) {
+                        continue;
+                    }
+                    Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
+                            + " from package " + bp.getPackageName());
+                    mRegistry.removePermission(bp.getName());
+                }
+            }
+        }
+        return changed;
+    }
+
+    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED
+            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(message + " requires "
+                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+        }
+    }
+
+    private void enforceGrantRevokeGetRuntimePermissionPermissions(@NonNull String message) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED
+            && mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED
+            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(message + " requires "
+                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS + " or "
+                    + Manifest.permission.GET_RUNTIME_PERMISSIONS);
+        }
+    }
+
+    /**
+     * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userId} is not for the caller.
+     *
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserPermission(int callingUid, @UserIdInt int userId,
+            boolean requireFullPermission, boolean checkShell, @Nullable String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (checkCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission)) {
+            return;
+        }
+        String errorMessage = buildInvalidCrossUserPermissionMessage(
+                callingUid, userId, message, requireFullPermission);
+        Slog.w(TAG, errorMessage);
+        throw new SecurityException(errorMessage);
+    }
+
+    /**
+     *  Enforces that if the caller is shell, it does not have the provided user restriction.
+     */
+    private void enforceShellRestriction(@NonNull String restriction, int callingUid,
+            @UserIdInt int userId) {
+        if (callingUid == Process.SHELL_UID) {
+            if (userId >= 0 && mUserManagerInt.hasUserRestriction(restriction, userId)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + userId);
+            } else if (userId < 0) {
+                Slog.e(LOG_TAG, "Unable to check shell permission for user "
+                        + userId + "\n\t" + Debug.getCallers(3));
+            }
+        }
+    }
+
+    private boolean checkCrossUserPermission(int callingUid, @UserIdInt int callingUserId,
+            @UserIdInt int userId, boolean requireFullPermission) {
+        if (userId == callingUserId) {
+            return true;
+        }
+        if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+            return true;
+        }
+        if (requireFullPermission) {
+            return checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        }
+        return checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                || checkCallingOrSelfPermission(android.Manifest.permission.INTERACT_ACROSS_USERS);
+    }
+
+    private boolean checkCallingOrSelfPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    @NonNull
+    private static String buildInvalidCrossUserPermissionMessage(int callingUid,
+            @UserIdInt int userId, @Nullable String message, boolean requireFullPermission) {
+        StringBuilder builder = new StringBuilder();
+        if (message != null) {
+            builder.append(message);
+            builder.append(": ");
+        }
+        builder.append("UID ");
+        builder.append(callingUid);
+        builder.append(" requires ");
+        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        if (!requireFullPermission) {
+            builder.append(" or ");
+            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        }
+        builder.append(" to access user ");
+        builder.append(userId);
+        builder.append(".");
+        return builder.toString();
+    }
+
+    @GuardedBy("mLock")
+    private int calculateCurrentPermissionFootprintLocked(@NonNull Permission permissionTree) {
+        int size = 0;
+        for (final Permission permission : mRegistry.getPermissions()) {
+            size += permissionTree.calculateFootprint(permission);
+        }
+        return size;
+    }
+
+    @GuardedBy("mLock")
+    private void enforcePermissionCapLocked(PermissionInfo info, Permission tree) {
+        // We calculate the max size of permissions defined by this uid and throw
+        // if that plus the size of 'info' would exceed our stated maximum.
+        if (tree.getUid() != Process.SYSTEM_UID) {
+            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
+            if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) {
+                throw new SecurityException("Permission tree size cap exceeded");
+            }
+        }
+    }
+
+    @Override
+    public void onSystemReady() {
+        // Now that we've scanned all packages, and granted any default
+        // permissions, ensure permissions are updated. Beware of dragons if you
+        // try optimizing this.
+        updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false);
+
+        final PermissionPolicyInternal permissionPolicyInternal = LocalServices.getService(
+                PermissionPolicyInternal.class);
+        permissionPolicyInternal.setOnInitializedCallback(userId ->
+                // The SDK updated case is already handled when we run during the ctor.
+                updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, false)
+        );
+
+        mSystemReady = true;
+
+        synchronized (mLock) {
+            if (mPrivappPermissionsViolations != null) {
+                throw new IllegalStateException("Signature|privileged permissions not in "
+                        + "privapp-permissions allowlist: " + mPrivappPermissionsViolations);
+            }
+        }
+
+        mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
+        mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
+    }
+
+    private static String getVolumeUuidForPackage(AndroidPackage pkg) {
+        if (pkg == null) {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+        if (pkg.isExternalStorage()) {
+            if (TextUtils.isEmpty(pkg.getVolumeUuid())) {
+                return StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                return pkg.getVolumeUuid();
+            }
+        } else {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+    }
+
+    private static boolean hasPermission(AndroidPackage pkg, String permName) {
+        if (pkg.getPermissions().isEmpty()) {
+            return false;
+        }
+
+        for (int i = pkg.getPermissions().size() - 1; i >= 0; i--) {
+            if (pkg.getPermissions().get(i).getName().equals(permName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Log that a permission request was granted/revoked.
+     *
+     * @param action the action performed
+     * @param name name of the permission
+     * @param packageName package permission is for
+     */
+    private void logPermission(int action, @NonNull String name, @NonNull String packageName) {
+        final LogMaker log = new LogMaker(action);
+        log.setPackageName(packageName);
+        log.addTaggedData(MetricsProto.MetricsEvent.FIELD_PERMISSION, name);
+
+        mMetricsLogger.write(log);
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private UidPermissionState getUidStateLocked(@NonNull PackageStateInternal ps,
+            @UserIdInt int userId) {
+        return getUidStateLocked(ps.getAppId(), userId);
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private UidPermissionState getUidStateLocked(@NonNull AndroidPackage pkg,
+            @UserIdInt int userId) {
+        return getUidStateLocked(pkg.getUid(), userId);
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private UidPermissionState getUidStateLocked(@AppIdInt int appId, @UserIdInt int userId) {
+        final UserPermissionState userState = mState.getUserState(userId);
+        if (userState == null) {
+            return null;
+        }
+        return userState.getUidState(appId);
+    }
+
+    private void removeUidStateAndResetPackageInstallPermissionsFixed(@AppIdInt int appId,
+            @NonNull String packageName, @UserIdInt int userId) {
+        synchronized (mLock) {
+            final UserPermissionState userState = mState.getUserState(userId);
+            if (userState == null) {
+                return;
+            }
+            userState.removeUidState(appId);
+            userState.setInstallPermissionsFixed(packageName, false);
+        }
+    }
+
+    @Override
+    public void readLegacyPermissionStateTEMP() {
+        final int[] userIds = getAllUserIds();
+        mPackageManagerInt.forEachPackageSetting(ps -> {
+            final int appId = ps.getAppId();
+            final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
+
+            synchronized (mLock) {
+                for (final int userId : userIds) {
+                    final UserPermissionState userState = mState.getOrCreateUserState(userId);
+
+                    userState.setInstallPermissionsFixed(ps.getPackageName(),
+                            ps.isInstallPermissionsFixed());
+                    final UidPermissionState uidState = userState.getOrCreateUidState(appId);
+                    uidState.reset();
+                    uidState.setMissing(legacyState.isMissing(userId));
+                    readLegacyPermissionStatesLocked(uidState,
+                            legacyState.getPermissionStates(userId));
+                }
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
+    private void readLegacyPermissionStatesLocked(@NonNull UidPermissionState uidState,
+            @NonNull Collection<LegacyPermissionState.PermissionState> permissionStates) {
+        for (final LegacyPermissionState.PermissionState permissionState : permissionStates) {
+            final String permissionName = permissionState.getName();
+            final Permission permission = mRegistry.getPermission(permissionName);
+            if (permission == null) {
+                Slog.w(TAG, "Unknown permission: " + permissionName);
+                continue;
+            }
+            uidState.putPermissionState(permission, permissionState.isGranted(),
+                    permissionState.getFlags());
+        }
+    }
+
+    @Override
+    public void writeLegacyPermissionStateTEMP() {
+        final int[] userIds;
+        synchronized (mLock) {
+            userIds = mState.getUserIds();
+        }
+        mPackageManagerInt.forEachPackageSetting(ps -> {
+            ps.setInstallPermissionsFixed(false);
+            final LegacyPermissionState legacyState = ps.getLegacyPermissionState();
+            legacyState.reset();
+            final int appId = ps.getAppId();
+
+            synchronized (mLock) {
+                for (final int userId : userIds) {
+                    final UserPermissionState userState = mState.getUserState(userId);
+                    if (userState == null) {
+                        Slog.e(TAG, "Missing user state for " + userId);
+                        continue;
+                    }
+
+                    if (userState.areInstallPermissionsFixed(ps.getPackageName())) {
+                        ps.setInstallPermissionsFixed(true);
+                    }
+
+                    final UidPermissionState uidState = userState.getUidState(appId);
+                    if (uidState == null) {
+                        Slog.e(TAG, "Missing permission state for " + ps.getPackageName()
+                                + " and user " + userId);
+                        continue;
+                    }
+
+                    legacyState.setMissing(uidState.isMissing(), userId);
+                    final List<PermissionState> permissionStates = uidState.getPermissionStates();
+                    final int permissionStatesSize = permissionStates.size();
+                    for (int i = 0; i < permissionStatesSize; i++) {
+                        final PermissionState permissionState = permissionStates.get(i);
+
+                        final LegacyPermissionState.PermissionState legacyPermissionState =
+                                new LegacyPermissionState.PermissionState(permissionState.getName(),
+                                        permissionState.getPermission().isRuntime(),
+                                        permissionState.isGranted(), permissionState.getFlags());
+                        legacyState.putPermissionState(legacyPermissionState, userId);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void readLegacyPermissionsTEMP(
+            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+        for (int readPermissionOrPermissionTree = 0; readPermissionOrPermissionTree < 2;
+                readPermissionOrPermissionTree++) {
+            final List<LegacyPermission> legacyPermissions = readPermissionOrPermissionTree == 0
+                    ? legacyPermissionSettings.getPermissions()
+                    : legacyPermissionSettings.getPermissionTrees();
+            synchronized (mLock) {
+                final int legacyPermissionsSize = legacyPermissions.size();
+                for (int i = 0; i < legacyPermissionsSize; i++) {
+                    final LegacyPermission legacyPermission = legacyPermissions.get(i);
+                    final Permission permission = new Permission(
+                            legacyPermission.getPermissionInfo(), legacyPermission.getType());
+                    if (readPermissionOrPermissionTree == 0) {
+                        // Config permissions are currently read in PermissionManagerService
+                        // constructor. The old behavior was to add other attributes to the config
+                        // permission in LegacyPermission.read(), so equivalently we can add the
+                        // GIDs to the new permissions here, since config permissions created in
+                        // PermissionManagerService constructor get only their names and GIDs there.
+                        final Permission configPermission = mRegistry.getPermission(
+                                permission.getName());
+                        if (configPermission != null
+                                && configPermission.getType() == Permission.TYPE_CONFIG) {
+                            permission.setGids(configPermission.getRawGids(),
+                                    configPermission.areGidsPerUser());
+                        }
+                        mRegistry.addPermission(permission);
+                    } else {
+                        mRegistry.addPermissionTree(permission);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void writeLegacyPermissionsTEMP(
+            @NonNull LegacyPermissionSettings legacyPermissionSettings) {
+        for (int writePermissionOrPermissionTree = 0; writePermissionOrPermissionTree < 2;
+                writePermissionOrPermissionTree++) {
+            final List<LegacyPermission> legacyPermissions = new ArrayList<>();
+            synchronized (mLock) {
+                final Collection<Permission> permissions = writePermissionOrPermissionTree == 0
+                        ? mRegistry.getPermissions() : mRegistry.getPermissionTrees();
+                for (final Permission permission : permissions) {
+                    // We don't need to provide UID and GIDs, which are only retrieved when dumping.
+                    final LegacyPermission legacyPermission = new LegacyPermission(
+                            permission.getPermissionInfo(), permission.getType(), 0,
+                            EmptyArray.INT);
+                    legacyPermissions.add(legacyPermission);
+                }
+            }
+            if (writePermissionOrPermissionTree == 0) {
+                legacyPermissionSettings.replacePermissions(legacyPermissions);
+            } else {
+                legacyPermissionSettings.replacePermissionTrees(legacyPermissions);
+            }
+        }
+    }
+
+    private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg) {
+        if (!pkg.getAdoptPermissions().isEmpty()) {
+            // This package wants to adopt ownership of permissions from
+            // another package.
+            for (int i = pkg.getAdoptPermissions().size() - 1; i >= 0; i--) {
+                final String origName = pkg.getAdoptPermissions().get(i);
+                if (canAdoptPermissionsInternal(origName, pkg)) {
+                    Slog.i(TAG, "Adopting permissions from " + origName + " to "
+                            + pkg.getPackageName());
+                    synchronized (mLock) {
+                        mRegistry.transferPermissions(origName, pkg.getPackageName());
+                    }
+                }
+            }
+        }
+
+        // Don't allow ephemeral applications to define new permissions groups.
+        if (isInstantApp) {
+            Slog.w(TAG, "Permission groups from package " + pkg.getPackageName()
+                    + " ignored: instant apps cannot define new permission groups.");
+        } else {
+            addAllPermissionGroupsInternal(pkg);
+        }
+
+        // If a permission has had its defining app changed, or it has had its protection
+        // upgraded, we need to revoke apps that hold it
+        final List<String> permissionsWithChangedDefinition;
+        // Don't allow ephemeral applications to define new permissions.
+        if (isInstantApp) {
+            permissionsWithChangedDefinition = null;
+            Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
+                    + " ignored: instant apps cannot define new permissions.");
+        } else {
+            permissionsWithChangedDefinition = addAllPermissionsInternal(pkg);
+        }
+
+        boolean hasOldPkg = oldPkg != null;
+        boolean hasPermissionDefinitionChanges =
+                !CollectionUtils.isEmpty(permissionsWithChangedDefinition);
+        if (hasOldPkg || hasPermissionDefinitionChanges) {
+            // We need to call revokeRuntimePermissionsIfGroupChanged async as permission
+            // revoke callbacks from this method might need to kill apps which need the
+            // mPackages lock on a different thread. This would dead lock.
+            AsyncTask.execute(() -> {
+                if (hasOldPkg) {
+                    revokeRuntimePermissionsIfGroupChangedInternal(pkg, oldPkg);
+                    revokeStoragePermissionsIfScopeExpandedInternal(pkg, oldPkg);
+                }
+                if (hasPermissionDefinitionChanges) {
+                    revokeRuntimePermissionsIfPermissionDefinitionChangedInternal(
+                            permissionsWithChangedDefinition);
+                }
+            });
+        }
+    }
+
+    private boolean canAdoptPermissionsInternal(@NonNull String oldPackageName,
+            @NonNull AndroidPackage newPkg) {
+        final PackageStateInternal oldPs =
+                mPackageManagerInt.getPackageStateInternal(oldPackageName);
+        if (oldPs == null) {
+            return false;
+        }
+        if (!oldPs.isSystem()) {
+            Slog.w(TAG, "Unable to update from " + oldPs.getPackageName()
+                    + " to " + newPkg.getPackageName()
+                    + ": old package not in system partition");
+            return false;
+        }
+        if (mPackageManagerInt.getPackage(oldPs.getPackageName()) != null) {
+            Slog.w(TAG, "Unable to update from " + oldPs.getPackageName()
+                    + " to " + newPkg.getPackageName()
+                    + ": old package still exists");
+            return false;
+        }
+        return true;
+    }
+
+    private void onPackageInstalledInternal(@NonNull AndroidPackage pkg, int previousAppId,
+            @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
+            @UserIdInt int[] userIds) {
+        // If previousAppId is not Process.INVALID_UID, the package is performing a migration out
+        // of a shared user group. Operations we need to do before calling updatePermissions():
+        // - Retrieve the original uid permission state and create a copy of it as the new app's
+        //   uid state. The new permission state will be properly updated in updatePermissions().
+        // - Remove the app from the original shared user group. Other apps in the shared
+        //   user group will perceive as if the original app is uninstalled.
+        if (previousAppId != Process.INVALID_UID) {
+            final PackageStateInternal ps =
+                    mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+            final List<AndroidPackage> origSharedUserPackages =
+                    mPackageManagerInt.getPackagesForAppId(previousAppId);
+
+            synchronized (mLock) {
+                // All users are affected
+                for (final int userId : getAllUserIds()) {
+                    // Retrieve the original uid state
+                    final UserPermissionState userState = mState.getUserState(userId);
+                    if (userState == null) {
+                        continue;
+                    }
+                    final UidPermissionState prevUidState = userState.getUidState(previousAppId);
+                    if (prevUidState == null) {
+                        continue;
+                    }
+
+                    // Insert new uid state by cloning the original one
+                    userState.createUidStateWithExisting(ps.getAppId(), prevUidState);
+
+                    // Remove original app ID from original shared user group
+                    // Should match the implementation of onPackageUninstalledInternal(...)
+                    if (origSharedUserPackages.isEmpty()) {
+                        removeUidStateAndResetPackageInstallPermissionsFixed(
+                                previousAppId, pkg.getPackageName(), userId);
+                    } else {
+                        revokeSharedUserPermissionsForLeavingPackageInternal(pkg, previousAppId,
+                                origSharedUserPackages, userId);
+                    }
+                }
+            }
+        }
+        updatePermissions(pkg.getPackageName(), pkg);
+        for (final int userId : userIds) {
+            addAllowlistedRestrictedPermissionsInternal(pkg,
+                    params.getAllowlistedRestrictedPermissions(),
+                    FLAG_PERMISSION_WHITELIST_INSTALLER, userId);
+            final int autoRevokePermissionsMode = params.getAutoRevokePermissionsMode();
+            if (autoRevokePermissionsMode == AppOpsManager.MODE_ALLOWED
+                    || autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED) {
+                // TODO: theianchen Bug: 182523293
+                // We should move this portion of code that's calling
+                // setAutoRevokeExemptedInternal() into the old PMS
+                setAutoRevokeExemptedInternal(pkg,
+                        autoRevokePermissionsMode == AppOpsManager.MODE_IGNORED, userId);
+            }
+            grantRequestedRuntimePermissionsInternal(pkg, params.getGrantedPermissions(), userId);
+        }
+    }
+
+    private void addAllowlistedRestrictedPermissionsInternal(@NonNull AndroidPackage pkg,
+            @NonNull List<String> allowlistedRestrictedPermissions,
+            @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId) {
+        List<String> permissions = getAllowlistedRestrictedPermissionsInternal(pkg, flags, userId);
+        if (permissions != null) {
+            ArraySet<String> permissionSet = new ArraySet<>(permissions);
+            permissionSet.addAll(allowlistedRestrictedPermissions);
+            permissions = new ArrayList<>(permissionSet);
+        } else {
+            permissions = allowlistedRestrictedPermissions;
+        }
+        setAllowlistedRestrictedPermissionsInternal(pkg, permissions, flags, userId);
+    }
+
+    private void onPackageRemovedInternal(@NonNull AndroidPackage pkg) {
+        removeAllPermissionsInternal(pkg);
+    }
+
+    private void onPackageUninstalledInternal(@NonNull String packageName, int appId,
+            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
+            @UserIdInt int[] userIds) {
+        // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin
+        //  a shared UID permission state.
+
+        // TODO: Move these checks to check PackageState to be more reliable.
+        // System packages should always have an available APK.
+        if (pkg != null && pkg.isSystem()
+                // We may be fully removing invalid system packages during boot, and in that case we
+                // do want to remove their permission state. So make sure that the package is only
+                // being marked as uninstalled instead of fully removed.
+                && mPackageManagerInt.getPackage(packageName) != null) {
+            // If we are only marking a system package as uninstalled, we need to keep its
+            // pregranted permission state so that it still works once it gets reinstalled, thus
+            // only reset the user modifications to its permission state.
+            for (final int userId : userIds) {
+                resetRuntimePermissionsInternal(pkg, userId);
+            }
+            return;
+        }
+        updatePermissions(packageName, null);
+        for (final int userId : userIds) {
+            if (sharedUserPkgs.isEmpty()) {
+                removeUidStateAndResetPackageInstallPermissionsFixed(appId, packageName, userId);
+            } else {
+                // Remove permissions associated with package. Since runtime
+                // permissions are per user we have to kill the removed package
+                // or packages running under the shared user of the removed
+                // package if revoking the permissions requested only by the removed
+                // package is successful and this causes a change in gids.
+                revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId,
+                        sharedUserPkgs, userId);
+            }
+        }
+    }
+
+    @NonNull
+    @Override
+    public List<LegacyPermission> getLegacyPermissions() {
+        synchronized (mLock) {
+            final List<LegacyPermission> legacyPermissions = new ArrayList<>();
+            for (final Permission permission : mRegistry.getPermissions()) {
+                final LegacyPermission legacyPermission = new LegacyPermission(
+                        permission.getPermissionInfo(), permission.getType(), permission.getUid(),
+                        permission.getRawGids());
+                legacyPermissions.add(legacyPermission);
+            }
+            return legacyPermissions;
+        }
+    }
+
+    @Override
+    public Map<String, Set<String>> getAllAppOpPermissionPackages() {
+        synchronized (mLock) {
+            final ArrayMap<String, ArraySet<String>> appOpPermissionPackages =
+                    mRegistry.getAllAppOpPermissionPackages();
+            final Map<String, Set<String>> deepClone = new ArrayMap<>();
+            final int appOpPermissionPackagesSize = appOpPermissionPackages.size();
+            for (int i = 0; i < appOpPermissionPackagesSize; i++) {
+                final String appOpPermission = appOpPermissionPackages.keyAt(i);
+                final ArraySet<String> packageNames = appOpPermissionPackages.valueAt(i);
+                deepClone.put(appOpPermission, new ArraySet<>(packageNames));
+            }
+            return deepClone;
+        }
+    }
+
+    @NonNull
+    @Override
+    public LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId) {
+        final LegacyPermissionState legacyState = new LegacyPermissionState();
+        synchronized (mLock) {
+            final int[] userIds = mState.getUserIds();
+            for (final int userId : userIds) {
+                final UidPermissionState uidState = getUidStateLocked(appId, userId);
+                if (uidState == null) {
+                    Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID "
+                            + userId);
+                    continue;
+                }
+
+                final List<PermissionState> permissionStates = uidState.getPermissionStates();
+                final int permissionStatesSize = permissionStates.size();
+                for (int i = 0; i < permissionStatesSize; i++) {
+                    final PermissionState permissionState = permissionStates.get(i);
+
+                    final LegacyPermissionState.PermissionState legacyPermissionState =
+                            new LegacyPermissionState.PermissionState(permissionState.getName(),
+                                    permissionState.getPermission().isRuntime(),
+                                    permissionState.isGranted(), permissionState.getFlags());
+                    legacyState.putPermissionState(legacyPermissionState, userId);
+                }
+            }
+        }
+        return legacyState;
+    }
+
+    @NonNull
+    @Override
+    public int[] getGidsForUid(int uid) {
+        final int appId = UserHandle.getAppId(uid);
+        final int userId = UserHandle.getUserId(uid);
+        synchronized (mLock) {
+            final UidPermissionState uidState = getUidStateLocked(appId, userId);
+            if (uidState == null) {
+                Slog.e(TAG, "Missing permissions state for app ID " + appId + " and user ID "
+                        + userId);
+                return EMPTY_INT_ARRAY;
+            }
+            return uidState.computeGids(mGlobalGids, userId);
+        }
+    }
+
+    @Override
+    public boolean isPermissionsReviewRequired(@NonNull String packageName,
+            @UserIdInt int userId) {
+        Objects.requireNonNull(packageName, "packageName");
+        // TODO(b/173235285): Some caller may pass USER_ALL as userId.
+        //Preconditions.checkArgumentNonnegative(userId, "userId");
+        return isPermissionsReviewRequiredInternal(packageName, userId);
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getGrantedPermissions(@NonNull String packageName,
+            @UserIdInt int userId) {
+        Objects.requireNonNull(packageName, "packageName");
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        return getGrantedPermissionsInternal(packageName, userId);
+    }
+
+    @NonNull
+    @Override
+    public int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId) {
+        Objects.requireNonNull(permissionName, "permissionName");
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        return getPermissionGidsInternal(permissionName, userId);
+    }
+
+    @NonNull
+    @Override
+    public String[] getAppOpPermissionPackages(@NonNull String permissionName) {
+        Objects.requireNonNull(permissionName, "permissionName");
+        return PermissionManagerServiceImpl.this.getAppOpPermissionPackagesInternal(permissionName);
+    }
+
+    @Override
+    public void onStorageVolumeMounted(@Nullable String volumeUuid, boolean fingerprintChanged) {
+        updateAllPermissions(volumeUuid, fingerprintChanged);
+    }
+
+    @Override
+    public void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId) {
+        Objects.requireNonNull(pkg, "pkg");
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        resetRuntimePermissionsInternal(pkg, userId);
+    }
+
+    @Override
+    public Permission getPermissionTEMP(String permName) {
+        synchronized (mLock) {
+            return mRegistry.getPermission(permName);
+        }
+    }
+
+    @NonNull
+    @Override
+    public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+            @PermissionInfo.Protection int protection) {
+        ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+
+        synchronized (mLock) {
+            for (final Permission permission : mRegistry.getPermissions()) {
+                if (permission.getProtection() == protection) {
+                    matchingPermissions.add(permission.generatePermissionInfo(0));
+                }
+            }
+        }
+
+        return matchingPermissions;
+    }
+
+    @NonNull
+    @Override
+    public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+            @PermissionInfo.ProtectionFlags int protectionFlags) {
+        ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+
+        synchronized (mLock) {
+            for (final Permission permission : mRegistry.getPermissions()) {
+                if ((permission.getProtectionFlags() & protectionFlags) == protectionFlags) {
+                    matchingPermissions.add(permission.generatePermissionInfo(0));
+                }
+            }
+        }
+
+        return matchingPermissions;
+    }
+
+    @Override
+    public void onUserCreated(@UserIdInt int userId) {
+        Preconditions.checkArgumentNonNegative(userId, "userId");
+        // NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
+        updateAllPermissions(StorageManager.UUID_PRIVATE_INTERNAL, true);
+    }
+
+    @Override
+    public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg) {
+        Objects.requireNonNull(pkg);
+        onPackageAddedInternal(pkg, isInstantApp, oldPkg);
+    }
+
+    @Override
+    public void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
+            @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
+            @UserIdInt int userId) {
+        Objects.requireNonNull(pkg, "pkg");
+        Objects.requireNonNull(params, "params");
+        Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
+                || userId == UserHandle.USER_ALL, "userId");
+        final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
+                : new int[] { userId };
+        onPackageInstalledInternal(pkg, previousAppId, params, userIds);
+    }
+
+    @Override
+    public void onPackageRemoved(@NonNull AndroidPackage pkg) {
+        Objects.requireNonNull(pkg);
+        onPackageRemovedInternal(pkg);
+    }
+
+    @Override
+    public void onPackageUninstalled(@NonNull String packageName, int appId,
+            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
+            @UserIdInt int userId) {
+        Objects.requireNonNull(packageName, "packageName");
+        Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs");
+        Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
+                || userId == UserHandle.USER_ALL, "userId");
+        final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
+                : new int[] { userId };
+        onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds);
+    }
+
+    private boolean setAutoRevokeExemptedInternal(@NonNull AndroidPackage pkg, boolean exempted,
+            @UserIdInt int userId) {
+        final int packageUid = UserHandle.getUid(userId, pkg.getUid());
+        if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_AUTO_REVOKE_MANAGED_BY_INSTALLER,
+                packageUid, pkg.getPackageName()) != MODE_ALLOWED) {
+            // Allowlist user set - don't override
+            return false;
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            mAppOpsManager.setMode(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageUid,
+                    pkg.getPackageName(), exempted ? MODE_IGNORED : MODE_ALLOWED);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return true;
+    }
+
+    /**
+     * Callbacks invoked when interesting actions have been taken on a permission.
+     * <p>
+     * NOTE: The current arguments are merely to support the existing use cases. This
+     * needs to be properly thought out with appropriate arguments for each of the
+     * callback methods.
+     */
+    private static class PermissionCallback {
+        public void onGidsChanged(@AppIdInt int appId, @UserIdInt int userId) {}
+        public void onPermissionChanged() {}
+        public void onPermissionGranted(int uid, @UserIdInt int userId) {}
+        public void onInstallPermissionGranted() {}
+        public void onPermissionRevoked(int uid, @UserIdInt int userId, String reason) {}
+        public void onInstallPermissionRevoked() {}
+        public void onPermissionUpdated(@UserIdInt int[] updatedUserIds, boolean sync) {}
+        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
+                int uid) {
+            onPermissionUpdated(updatedUserIds, sync);
+        }
+        public void onPermissionRemoved() {}
+        public void onInstallPermissionUpdated() {}
+        public void onInstallPermissionUpdatedNotifyListener(int uid) {
+            onInstallPermissionUpdated();
+        }
+    }
+
+    private static final class OnPermissionChangeListeners extends Handler {
+        private static final int MSG_ON_PERMISSIONS_CHANGED = 1;
+
+        private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners =
+                new RemoteCallbackList<>();
+
+        OnPermissionChangeListeners(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_PERMISSIONS_CHANGED: {
+                    final int uid = msg.arg1;
+                    handleOnPermissionsChanged(uid);
+                } break;
+            }
+        }
+
+        public void addListener(IOnPermissionsChangeListener listener) {
+            mPermissionListeners.register(listener);
+        }
+
+        public void removeListener(IOnPermissionsChangeListener listener) {
+            mPermissionListeners.unregister(listener);
+        }
+
+        public void onPermissionsChanged(int uid) {
+            if (mPermissionListeners.getRegisteredCallbackCount() > 0) {
+                obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
+            }
+        }
+
+        private void handleOnPermissionsChanged(int uid) {
+            final int count = mPermissionListeners.beginBroadcast();
+            try {
+                for (int i = 0; i < count; i++) {
+                    IOnPermissionsChangeListener callback = mPermissionListeners
+                            .getBroadcastItem(i);
+                    try {
+                        callback.onPermissionsChanged(uid);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Permission listener is dead", e);
+                    }
+                }
+            } finally {
+                mPermissionListeners.finishBroadcast();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
new file mode 100644
index 0000000..d2018f2
--- /dev/null
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -0,0 +1,597 @@
+/*
+ * 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.permission;
+
+import android.annotation.AppIdInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManagerInternal;
+
+import com.android.server.pm.parsing.pkg.AndroidPackage;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for managing all permissions and handling permissions related tasks.
+ */
+public interface PermissionManagerServiceInterface extends PermissionManagerInternal {
+    /**
+     * Dump.
+     */
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    /**
+     * Retrieve all of the known permission groups in the system.
+     *
+     * @param flags additional option flags to modify the data returned
+     * @return a list of {@link PermissionGroupInfo} containing information about all of the known
+     *         permission groups
+     */
+    List<PermissionGroupInfo> getAllPermissionGroups(
+            @PackageManager.PermissionGroupInfoFlags int flags);
+
+    /**
+     * Retrieve all of the information we know about a particular group of permissions.
+     *
+     * @param groupName the fully qualified name (e.g. com.android.permission_group.APPS) of the
+     *                  permission you are interested in
+     * @param flags additional option flags to modify the data returned
+     * @return a {@link PermissionGroupInfo} containing information about the permission, or
+     *         {@code null} if not found
+     */
+    PermissionGroupInfo getPermissionGroupInfo(String groupName,
+            @PackageManager.PermissionGroupInfoFlags int flags);
+
+    /**
+     * Retrieve all of the information we know about a particular permission.
+     *
+     * @param permName the fully qualified name (e.g. com.android.permission.LOGIN) of the
+     *                       permission you are interested in
+     * @param flags additional option flags to modify the data returned
+     * @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);
+
+    /**
+     * Query for all of the permissions associated with a particular group.
+     *
+     * @param groupName the fully qualified name (e.g. com.android.permission.LOGIN) of the
+     *                  permission group you are interested in. Use {@code null} to find all of the
+     *                  permissions not associated with a group
+     * @param flags additional option flags to modify the data returned
+     * @return a list of {@link PermissionInfo} containing information about all of the permissions
+     *         in the given group, or {@code null} if the group is not found
+     */
+    List<PermissionInfo> queryPermissionsByGroup(String groupName,
+            @PackageManager.PermissionInfoFlags int flags);
+
+    /**
+     * Add a new dynamic permission to the system. For this to work, your package must have defined
+     * a permission tree through the
+     * {@link android.R.styleable#AndroidManifestPermissionTree &lt;permission-tree&gt;} tag in its
+     * manifest. A package can only add permissions to trees that were defined by either its own
+     * package or another with the same user id; a permission is in a tree if it matches the name of
+     * the permission tree + ".": for example, "com.foo.bar" is a member of the permission tree
+     * "com.foo".
+     * <p>
+     * It is good to make your permission tree name descriptive, because you are taking possession
+     * of that entire set of permission names. Thus, it must be under a domain you control, with a
+     * suffix that will not match any normal permissions that may be declared in any applications
+     * that are part of that domain.
+     * <p>
+     * New permissions must be added before any .apks are installed that use those permissions.
+     * Permissions you add through this method are remembered across reboots of the device. If the
+     * given permission already exists, the info you supply here will be used to update it.
+     *
+     * @param info description of the permission to be added
+     * @param async whether the persistence of the permission should be asynchronous, allowing it to
+     *              return quicker and batch a series of adds, at the expense of no guarantee the
+     *              added permission will be retained if the device is rebooted before it is
+     *              written.
+     * @return {@code true} if a new permission was created, {@code false} if an existing one was
+     *         updated
+     * @throws SecurityException if you are not allowed to add the given permission name
+     *
+     * @see #removePermission(String)
+     */
+    boolean addPermission(PermissionInfo info, boolean async);
+
+    /**
+     * Removes a permission that was previously added with
+     * {@link #addPermission(PermissionInfo, boolean)}. The same ownership rules apply -- you are
+     * only allowed to remove permissions that you are allowed to add.
+     *
+     * @param permName the name of the permission to remove
+     * @throws SecurityException if you are not allowed to remove the given permission name
+     *
+     * @see #addPermission(PermissionInfo, boolean)
+     */
+    void removePermission(String permName);
+
+    /**
+     * Gets the state flags associated with a permission.
+     *
+     * @param packageName the package name for which to get the flags
+     * @param permName the permission for which to get the flags
+     * @param userId the user for which to get permission flags
+     * @return the permission flags
+     */
+    int getPermissionFlags(String packageName, String permName, int userId);
+
+    /**
+     * Updates the flags associated with a permission by replacing the flags in the specified mask
+     * with the provided flag values.
+     *
+     * @param packageName The package name for which to update the flags
+     * @param permName The permission for which to update the flags
+     * @param flagMask The flags which to replace
+     * @param flagValues The flags with which to replace
+     * @param userId The user for which to update the permission flags
+     */
+    void updatePermissionFlags(String packageName, String permName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
+
+    /**
+     * Update the permission flags for all packages and runtime permissions of a user in order
+     * to allow device or profile owner to remove POLICY_FIXED.
+     */
+    void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId);
+
+    /**
+     * TODO: theianchen We should get rid of the IBinder interface which is an implementation detail
+     *
+     * Add a listener for permission changes for installed packages.
+     * @param listener the listener to add
+     */
+    void addOnPermissionsChangeListener(IOnPermissionsChangeListener listener);
+
+    /**
+     * Remove a listener for permission changes for installed packages.
+     * @param listener the listener to remove
+     */
+    void removeOnPermissionsChangeListener(IOnPermissionsChangeListener listener);
+
+    /**
+     * addAllowlistedRestrictedPermission. TODO: theianchen add doc
+     */
+    boolean addAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags,
+            @UserIdInt int userId);
+
+    /**
+     * Gets the restricted permissions that have been allowlisted and the app is allowed to have
+     * them granted in their full form.
+     * <p>
+     * Permissions can be hard restricted which means that the app cannot hold them or soft
+     * restricted where the app can hold the permission but in a weaker form. Whether a permission
+     * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or
+     * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission
+     * declaration. Allowlisting a hard restricted permission allows for the to hold that permission
+     * and allowlisting a soft restricted permission allows the app to hold the permission in its
+     * full, unrestricted form.
+     * <p>
+     * There are four allowlists:
+     * <ol>
+     * <li>
+     * One for cases where the system permission policy allowlists a permission. This list
+     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be
+     * accessed by pre-installed holders of a dedicated permission.
+     * <li>
+     * One for cases where the system allowlists the permission when upgrading from an OS version in
+     * which the permission was not restricted to an OS version in which the permission is
+     * restricted. This list corresponds to the
+     * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by
+     * pre-installed holders of a dedicated permission or the installer on record.
+     * <li>
+     * One for cases where the installer of the package allowlists a permission. This list
+     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
+     * accessed by pre-installed holders of a dedicated permission or the installer on record.
+     * </ol>
+     *
+     * @param packageName the app for which to get allowlisted permissions
+     * @param flags the flag to determine which allowlist to query. Only one flag can be
+     *                      passed.
+     * @return the allowlisted permissions that are on any of the allowlists you query for
+     * @throws SecurityException if you try to access a allowlist that you have no access to
+     *
+     * @see #addAllowlistedRestrictedPermission(String, String, int)
+     * @see #removeAllowlistedRestrictedPermission(String, String, int)
+     * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
+     * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
+     * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
+     */
+    List<String> getAllowlistedRestrictedPermissions(@NonNull String packageName,
+            @PackageManager.PermissionWhitelistFlags int flags, @UserIdInt int userId);
+
+    /**
+     * Removes a allowlisted restricted permission for an app.
+     * <p>
+     * Permissions can be hard restricted which means that the app cannot hold them or soft
+     * restricted where the app can hold the permission but in a weaker form. Whether a permission
+     * is {@link PermissionInfo#FLAG_HARD_RESTRICTED hard restricted} or
+     * {@link PermissionInfo#FLAG_SOFT_RESTRICTED soft restricted} depends on the permission
+     * declaration. Allowlisting a hard restricted permission allows for the to hold that permission
+     * and allowlisting a soft restricted permission allows the app to hold the permission in its
+     * full, unrestricted form.
+     * <p>There are four allowlists:
+     * <ol>
+     * <li>
+     * One for cases where the system permission policy allowlists a permission. This list
+     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM} flag. Can only be
+     * accessed by pre-installed holders of a dedicated permission.
+     * <li>
+     * One for cases where the system allowlists the permission when upgrading from an OS version in
+     * which the permission was not restricted to an OS version in which the permission is
+     * restricted. This list corresponds to the
+     * {@link PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE} flag. Can be accessed by
+     * pre-installed holders of a dedicated permission or the installer on record.
+     * <li>
+     * One for cases where the installer of the package allowlists a permission. This list
+     * corresponds to the {@link PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER} flag. Can be
+     * accessed by pre-installed holders of a dedicated permission or the installer on record.
+     * </ol>
+     * <p>
+     * You need to specify the allowlists for which to set the allowlisted permissions which will
+     * clear the previous allowlisted permissions and replace them with the provided ones.
+     *
+     * @param packageName the app for which to get allowlisted permissions
+     * @param permName the allowlisted permission to remove
+     * @param flags the allowlists from which to remove. Passing multiple flags updates all
+     *                       specified allowlists.
+     * @return whether the permission was removed from the allowlist
+     * @throws SecurityException if you try to modify a allowlist that you have no access to.
+     *
+     * @see #getAllowlistedRestrictedPermissions(String, int)
+     * @see #addAllowlistedRestrictedPermission(String, String, int)
+     * @see PackageManager#FLAG_PERMISSION_WHITELIST_SYSTEM
+     * @see PackageManager#FLAG_PERMISSION_WHITELIST_UPGRADE
+     * @see PackageManager#FLAG_PERMISSION_WHITELIST_INSTALLER
+     */
+    boolean removeAllowlistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permName, @PackageManager.PermissionWhitelistFlags int flags,
+            @UserIdInt int userId);
+
+    /**
+     * Grant a runtime permission to an application which the application does not already have. The
+     * permission must have been requested by the application. If the application is not allowed to
+     * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or
+     * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown.
+     * <p>
+     * <strong>Note: </strong>Using this API requires holding
+     * {@code android.permission.GRANT_RUNTIME_PERMISSIONS} and if the user ID is not the current
+     * user {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param packageName the package to which to grant the permission
+     * @param permName the permission name to grant
+     * @param userId the user for which to grant the permission
+     *
+     * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String)
+     */
+    void grantRuntimePermission(String packageName, String permName, int userId);
+
+    /**
+     * Revoke a runtime permission that was previously granted by
+     * {@link #grantRuntimePermission(String, String, android.os.UserHandle)}. The permission must
+     * have been requested by and granted to the application. If the application is not allowed to
+     * hold the permission, a {@link java.lang.SecurityException} is thrown. If the package or
+     * permission is invalid, a {@link java.lang.IllegalArgumentException} is thrown.
+     * <p>
+     * <strong>Note: </strong>Using this API requires holding
+     * {@code android.permission.REVOKE_RUNTIME_PERMISSIONS} and if the user ID is not the current
+     * user {@code android.permission.INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @param packageName the package from which to revoke the permission
+     * @param permName the permission name to revoke
+     * @param userId the user for which to revoke the permission
+     * @param reason the reason for the revoke, or {@code null} for unspecified
+     *
+     * @see #grantRuntimePermission(String, String, android.os.UserHandle)
+     */
+    void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason);
+
+    /**
+     * Get whether you should show UI with rationale for requesting a permission. You should do this
+     * only if you do not have the permission and the context in which the permission is requested
+     * does not clearly communicate to the user what would be the benefit from grating this
+     * permission.
+     *
+     * @param permName a permission your app wants to request
+     * @return whether you can show permission rationale UI
+     */
+    boolean shouldShowRequestPermissionRationale(String packageName, String permName,
+            @UserIdInt int userId);
+
+    /**
+     * Checks whether a particular permissions has been revoked for a package by policy. Typically
+     * the device owner or the profile owner may apply such a policy. The user cannot grant policy
+     * revoked permissions, hence the only way for an app to get such a permission is by a policy
+     * change.
+     *
+     * @param packageName the name of the package you are checking against
+     * @param permName the name of the permission you are checking for
+     *
+     * @return whether the permission is restricted by policy
+     */
+    boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId);
+
+    /**
+     * Get set of permissions that have been split into more granular or dependent permissions.
+     *
+     * <p>E.g. before {@link android.os.Build.VERSION_CODES#Q} an app that was granted
+     * {@link Manifest.permission#ACCESS_COARSE_LOCATION} could access the location while it was in
+     * foreground and background. On platforms after {@link android.os.Build.VERSION_CODES#Q}
+     * the location permission only grants location access while the app is in foreground. This
+     * would break apps that target before {@link android.os.Build.VERSION_CODES#Q}. Hence whenever
+     * such an old app asks for a location permission (i.e. the
+     * {@link PermissionManager.SplitPermissionInfo#getSplitPermission()}), then the
+     * {@link Manifest.permission#ACCESS_BACKGROUND_LOCATION} permission (inside
+     * {@link PermissionManager.SplitPermissionInfo#getNewPermissions}) is added.
+     *
+     * <p>Note: Regular apps do not have to worry about this. The platform and permission controller
+     * automatically add the new permissions where needed.
+     *
+     * @return All permissions that are split.
+     */
+    List<SplitPermissionInfoParcelable> getSplitPermissions();
+
+    /**
+     * TODO:theianchen add doc describing this is the old checkPermissionImpl
+     */
+    int checkPermission(String pkgName, String permName, int userId);
+
+    /**
+     * TODO:theianchen add doc describing this is the old checkUidPermissionImpl
+     */
+    int checkUidPermission(int uid, String permName);
+
+    /**
+     * Adds a listener for runtime permission state (permissions or flags) changes.
+     *
+     * @param listener The listener.
+     */
+    void addOnRuntimePermissionStateChangedListener(
+            @NonNull PermissionManagerServiceInternal
+                    .OnRuntimePermissionStateChangedListener listener);
+
+    /**
+     * Removes a listener for runtime permission state (permissions or flags) changes.
+     *
+     * @param listener The listener.
+     */
+    void removeOnRuntimePermissionStateChangedListener(
+            @NonNull PermissionManagerServiceInternal
+                    .OnRuntimePermissionStateChangedListener listener);
+
+    /**
+     * Get all the package names requesting app op permissions.
+     *
+     * @return a map of app op permission names to package names requesting them
+     */
+    Map<String, Set<String>> getAllAppOpPermissionPackages();
+
+    /**
+     * Get whether permission review is required for a package.
+     *
+     * @param packageName the name of the package
+     * @param userId the user ID
+     * @return whether permission review is required
+     */
+    boolean isPermissionsReviewRequired(@NonNull String packageName,
+            @UserIdInt int userId);
+
+    /**
+     * Reset the runtime permission state changes for a package.
+     *
+     * TODO(zhanghai): Turn this into package change callback?
+     *
+     * @param pkg the package
+     * @param userId the user ID
+     */
+    void resetRuntimePermissions(@NonNull AndroidPackage pkg,
+            @UserIdInt int userId);
+
+    /**
+     * Read legacy permission state from package settings.
+     *
+     * TODO(zhanghai): This is a temporary method because we should not expose
+     * {@code PackageSetting} which is a implementation detail that permission should not know.
+     * Instead, it should retrieve the legacy state via a defined API.
+     */
+    void readLegacyPermissionStateTEMP();
+
+    /**
+     * Write legacy permission state to package settings.
+     *
+     * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence
+     * for permission.
+     */
+    void writeLegacyPermissionStateTEMP();
+
+    /**
+     * Get all the permissions granted to a package.
+     *
+     * @param packageName the name of the package
+     * @param userId the user ID
+     * @return the names of the granted permissions
+     */
+    @NonNull
+    Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId);
+
+    /**
+     * Get the GIDs of a permission.
+     *
+     * @param permissionName the name of the permission
+     * @param userId the user ID
+     * @return the GIDs of the permission
+     */
+    @NonNull
+    int[] getPermissionGids(@NonNull String permissionName, @UserIdInt int userId);
+
+    /**
+     * Get the packages that have requested an app op permission.
+     *
+     * @param permissionName the name of the app op permission
+     * @return the names of the packages that have requested the app op permission
+     */
+    @NonNull
+    String[] getAppOpPermissionPackages(@NonNull String permissionName);
+
+    /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
+    @Nullable
+    Permission getPermissionTEMP(@NonNull String permName);
+
+    /** Get all permissions that have a certain protection */
+    @NonNull
+    ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+            @PermissionInfo.Protection int protection);
+
+    /** Get all permissions that have certain protection flags */
+    @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+            @PermissionInfo.ProtectionFlags int protectionFlags);
+
+    /**
+     * Get all the legacy permissions currently registered in the system.
+     *
+     * @return the legacy permissions
+     */
+    @NonNull
+    List<LegacyPermission> getLegacyPermissions();
+
+    /**
+     * Get the legacy permission state of an app ID, either a package or a shared user.
+     *
+     * @param appId the app ID
+     * @return the legacy permission state
+     */
+    @NonNull
+    LegacyPermissionState getLegacyPermissionState(@AppIdInt int appId);
+
+    /**
+     * Read legacy permissions from legacy permission settings.
+     *
+     * TODO(zhanghai): This is a temporary method because we should not expose
+     * {@code LegacyPermissionSettings} which is a implementation detail that permission should not
+     * know. Instead, it should retrieve the legacy permissions via a defined API.
+     */
+    void readLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings);
+
+    /**
+     * Write legacy permissions to legacy permission settings.
+     *
+     * TODO(zhanghai): This is a temporary method and should be removed once we migrated persistence
+     * for permission.
+     */
+    void writeLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings);
+
+    /**
+     * Callback when the system is ready.
+     */
+    void onSystemReady();
+
+    /**
+     * Callback when a storage volume is mounted, so that all packages on it become available.
+     *
+     * @param volumeUuid the UUID of the storage volume
+     * @param fingerprintChanged whether the current build fingerprint is different from what it was
+     *                           when this volume was last mounted
+     */
+    void onStorageVolumeMounted(@NonNull String volumeUuid, boolean fingerprintChanged);
+
+    /**
+     * Get the GIDs computed from the permission state of a UID, either a package or a shared user.
+     *
+     * @param uid the UID
+     * @return the GIDs for the UID
+     */
+    @NonNull
+    int[] getGidsForUid(int uid);
+
+    /**
+     * Callback when a user has been created.
+     *
+     * @param userId the created user ID
+     */
+    void onUserCreated(@UserIdInt int userId);
+
+    /**
+     * Callback when a user has been removed.
+     *
+     * @param userId the removed user ID
+     */
+    void onUserRemoved(@UserIdInt int userId);
+
+    /**
+     * Callback when a package has been added.
+     *
+     * @param pkg the added package
+     * @param isInstantApp whether the added package is an instant app
+     * @param oldPkg the old package, or {@code null} if none
+     */
+    void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+            @Nullable AndroidPackage oldPkg);
+
+    /**
+     * Callback when a package has been installed for a user.
+     *
+     * @param pkg the installed package
+     * @param previousAppId the previous app ID if the package is leaving a shared UID,
+     * or Process.INVALID_UID
+     * @param params the parameters passed in for package installation
+     * @param userId the user ID this package is installed for
+     */
+    void onPackageInstalled(@NonNull AndroidPackage pkg, int previousAppId,
+            @NonNull PermissionManagerServiceInternal.PackageInstalledParams params,
+            @UserIdInt int userId);
+
+    /**
+     * Callback when a package has been removed.
+     *
+     * @param pkg the removed package
+     */
+    void onPackageRemoved(@NonNull AndroidPackage pkg);
+
+    /**
+     * Callback when a package has been uninstalled.
+     * <p>
+     * The package may have been fully removed from the system, or only marked as uninstalled for
+     * this user but still instlaled for other users.
+     *
+     * TODO: Pass PackageState instead.
+     *
+     * @param packageName the name of the uninstalled package
+     * @param appId the app ID of the uninstalled package
+     * @param pkg the uninstalled package, or {@code null} if unavailable
+     * @param sharedUserPkgs the packages that are in the same shared user
+     * @param userId the user ID the package is uninstalled for
+     */
+    void onPackageUninstalled(@NonNull String packageName, int appId, @Nullable AndroidPackage pkg,
+            @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
+}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
index 0b48b5c..2208e3c 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java
@@ -70,8 +70,11 @@
                 break;
             default:
                 if (!proxy.isCallerVerifier(callingUid)) {
-                    throw new SecurityException(
-                            "Caller is not allowed to query domain verification state");
+                    mContext.enforcePermission(android.Manifest.permission.DUMP,
+                            Binder.getCallingPid(), callingUid,
+                            "Caller " + callingUid
+                                    + " is not allowed to query domain verification state");
+                    break;
                 }
 
                 mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
index 471f38a..25147d0 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java
@@ -391,7 +391,7 @@
      */
     @ApprovalLevel
     int approvalLevelForDomain(@NonNull PackageStateInternal pkgSetting, @NonNull Intent intent,
-            @PackageManager.ResolveInfoFlags long resolveInfoFlags, @UserIdInt int userId);
+            @PackageManager.ResolveInfoFlagsBits long resolveInfoFlags, @UserIdInt int userId);
 
     /**
      * @return the domain verification set ID for the given package, or null if the ID is
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 661e67d..a3b0e3e 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
@@ -1197,6 +1197,7 @@
             @Nullable @UserIdInt Integer userId,
             @NonNull Function<String, PackageStateInternal> pkgSettingFunction)
             throws NameNotFoundException {
+        mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
         synchronized (mLock) {
             mDebug.printState(writer, packageName, userId, pkgSettingFunction, mAttachedPkgStates);
         }
@@ -1721,7 +1722,7 @@
 
     @Override
     public int approvalLevelForDomain(@NonNull PackageStateInternal pkgSetting,
-            @NonNull Intent intent, @PackageManager.ResolveInfoFlags long resolveInfoFlags,
+            @NonNull Intent intent, @PackageManager.ResolveInfoFlagsBits long resolveInfoFlags,
             @UserIdInt int userId) {
         String packageName = pkgSetting.getPackageName();
         if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) {
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 12cce0d..058b605 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -49,7 +49,7 @@
     }
 
     public static boolean isDomainVerificationIntent(Intent intent,
-            @PackageManager.ResolveInfoFlags long resolveInfoFlags) {
+            @PackageManager.ResolveInfoFlagsBits long resolveInfoFlags) {
         if (!intent.isWebIntent()) {
             return false;
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5c15a84..298f102 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -523,6 +523,7 @@
     private boolean mPendingKeyguardOccluded;
     private boolean mKeyguardOccludedChanged;
 
+    private ActivityTaskManagerInternal.SleepTokenAcquirer mScreenOffSleepTokenAcquirer;
     Intent mHomeIntent;
     Intent mCarDockIntent;
     Intent mDeskDockIntent;
@@ -1852,6 +1853,9 @@
                 new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
         mLogger = new MetricsLogger();
 
+        mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
+                .createSleepTokenAcquirer("ScreenOff");
+
         Resources res = mContext.getResources();
         mWakeOnDpadKeyPress =
                 res.getBoolean(com.android.internal.R.bool.config_wakeOnDpadKeyPress);
@@ -4521,6 +4525,7 @@
         if (DEBUG_WAKEUP) Slog.i(TAG, "Display" + displayId + " turned off...");
 
         if (displayId == DEFAULT_DISPLAY) {
+            updateScreenOffSleepToken(true);
             mRequestedOrSleepingDefaultDisplay = false;
             mDefaultDisplayPolicy.screenTurnedOff();
             synchronized (mLock) {
@@ -4553,6 +4558,7 @@
         if (displayId == DEFAULT_DISPLAY) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
                     0 /* cookie */);
+            updateScreenOffSleepToken(false);
             mDefaultDisplayPolicy.screenTurnedOn(screenOnListener);
             mBootAnimationDismissable = false;
 
@@ -5073,6 +5079,15 @@
         }
     }
 
+    // TODO (multidisplay): Support multiple displays in WindowManagerPolicy.
+    private void updateScreenOffSleepToken(boolean acquire) {
+        if (acquire) {
+            mScreenOffSleepTokenAcquirer.acquire(DEFAULT_DISPLAY);
+        } else {
+            mScreenOffSleepTokenAcquirer.release(DEFAULT_DISPLAY);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public void enableScreenAfterBoot() {
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 51bd745..9127484 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -19,6 +19,7 @@
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.view.Display;
 
 /**
  * Used to store power related requests to every display in a
@@ -33,9 +34,10 @@
 
     private final DisplayPowerRequest mDisplayPowerRequest;
     private final boolean mSupportsSandman;
+    private final int mGroupId;
 
-    // True if DisplayManagerService has applied all the latest display states that were
-    // requested for this group
+    // True if DisplayManagerService has applied all the latest display states that were requested
+    // for this group
     private boolean mReady;
     // True if this group is in the process of powering on
     private boolean mPoweringOn;
@@ -49,8 +51,9 @@
     private long mLastUserActivityTime;
     private long mLastUserActivityTimeNoChangeLights;
 
-    PowerGroup(DisplayPowerRequest displayPowerRequest, int wakefulness, boolean ready,
+    PowerGroup(int groupId, DisplayPowerRequest displayPowerRequest, int wakefulness, boolean ready,
             boolean supportsSandman) {
+        this.mGroupId = groupId;
         this.mDisplayPowerRequest = displayPowerRequest;
         this.mWakefulness = wakefulness;
         this.mReady = ready;
@@ -58,6 +61,7 @@
     }
 
     PowerGroup() {
+        this.mGroupId = Display.DEFAULT_DISPLAY_GROUP;
         this.mDisplayPowerRequest = new DisplayPowerRequest();
         this.mWakefulness = WAKEFULNESS_AWAKE;
         this.mReady = false;
@@ -72,6 +76,10 @@
         return mWakefulness;
     }
 
+    int getGroupId() {
+        return mGroupId;
+    }
+
     /**
      * Sets the {@code wakefulness} value for this {@link PowerGroup}.
      *
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 29166b3..1455326 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -104,7 +104,6 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.util.Preconditions;
@@ -117,7 +116,6 @@
 import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
-import com.android.server.display.DisplayGroup;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.policy.WindowManagerPolicy;
@@ -619,10 +617,6 @@
     @GuardedBy("mLock")
     private final SparseArray<PowerGroup> mPowerGroups = new SparseArray<>();
 
-    // A cached array of DisplayGroup Ids.
-    @GuardedBy("mLock")
-    private int[] mDisplayGroupIds;
-
     // We are currently in the middle of a batch change of uids.
     private boolean mUidsChanging;
 
@@ -665,13 +659,13 @@
                 // For now, only the default group supports sandman (dream/AOD).
                 final boolean supportsSandman = groupId == Display.DEFAULT_DISPLAY_GROUP;
                 final PowerGroup powerGroup = new PowerGroup(
+                        groupId,
                         new DisplayPowerRequest(),
                         getGlobalWakefulnessLocked(),
                         /* ready= */ false,
                         supportsSandman);
                 mPowerGroups.append(groupId, powerGroup);
-                mDisplayGroupIds = ArrayUtils.appendInt(mDisplayGroupIds, groupId);
-                onDisplayGroupEventLocked(DISPLAY_GROUP_ADDED, groupId);
+                onPowerGroupEventLocked(DISPLAY_GROUP_ADDED, powerGroup);
             }
         }
 
@@ -682,20 +676,22 @@
                     Slog.wtf(TAG, "Tried to remove default display group: " + groupId);
                     return;
                 }
-                mDisplayGroupIds = ArrayUtils.removeInt(mDisplayGroupIds, groupId);
                 if (!mPowerGroups.contains(groupId)) {
                     Slog.e(TAG, "Tried to remove non-existent group:" + groupId);
                     return;
                 }
-                mPowerGroups.delete(groupId);
-                onDisplayGroupEventLocked(DISPLAY_GROUP_REMOVED, groupId);
+                onPowerGroupEventLocked(DISPLAY_GROUP_REMOVED, mPowerGroups.get(groupId));
             }
         }
 
         @Override
         public void onDisplayGroupChanged(int groupId) {
             synchronized (mLock) {
-                onDisplayGroupEventLocked(DISPLAY_GROUP_CHANGED, groupId);
+                if (!mPowerGroups.contains(groupId)) {
+                    Slog.e(TAG, "Tried to change non-existent group: " + groupId);
+                    return;
+                }
+                onPowerGroupEventLocked(DISPLAY_GROUP_CHANGED, mPowerGroups.get(groupId));
             }
         }
     }
@@ -1150,7 +1146,7 @@
 
                 updatePowerStateLocked();
                 if (sQuiescent) {
-                    sleepDisplayGroupNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP,
+                    sleepDisplayGroupNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
                             mClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
@@ -1169,7 +1165,6 @@
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
             mAttentionDetector.systemReady(mContext);
             mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP, new PowerGroup());
-            mDisplayGroupIds = new int[]{Display.DEFAULT_DISPLAY_GROUP};
             mDisplayGroupPowerChangeListener = new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(mDisplayGroupPowerChangeListener);
 
@@ -1506,10 +1501,10 @@
                 opPackageName = wakeLock.mPackageName;
                 opUid = wakeLock.mOwnerUid;
             }
-            for (int id : mDisplayGroupIds) {
-                wakeDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(),
-                        PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
-                        opUid, opPackageName, opUid);
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                wakeDisplayGroupNoUpdateLocked(mPowerGroups.valueAt(idx), mClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag, opUid, opPackageName,
+                        opUid);
             }
         }
     }
@@ -1745,7 +1740,8 @@
             if (groupId == Display.INVALID_DISPLAY_GROUP) {
                 return;
             }
-            if (userActivityNoUpdateLocked(groupId, eventTime, event, flags, uid)) {
+            if (userActivityNoUpdateLocked(mPowerGroups.get(groupId), eventTime, event, flags,
+                    uid)) {
                 updatePowerStateLocked();
             }
         }
@@ -1753,8 +1749,10 @@
 
     private void onUserAttention() {
         synchronized (mLock) {
-            if (userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis(),
-                    PowerManager.USER_ACTIVITY_EVENT_ATTENTION, 0 /* flags */,
+            if (userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                    mClock.uptimeMillis(),
+                    PowerManager.USER_ACTIVITY_EVENT_ATTENTION,
+                    0 /* flags */,
                     Process.SYSTEM_UID)) {
                 updatePowerStateLocked();
             }
@@ -1764,8 +1762,9 @@
     @GuardedBy("mLock")
     private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
         boolean updatePowerState = false;
-        for (int id : mDisplayGroupIds) {
-            if (userActivityNoUpdateLocked(id, eventTime, event, flags, uid)) {
+        for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+            if (userActivityNoUpdateLocked(mPowerGroups.valueAt(idx), eventTime, event, flags,
+                    uid)) {
                 updatePowerState = true;
             }
         }
@@ -1774,10 +1773,10 @@
     }
 
     @GuardedBy("mLock")
-    private boolean userActivityNoUpdateLocked(int groupId, long eventTime, int event, int flags,
-            int uid) {
+    private boolean userActivityNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
+            int event, int flags, int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + groupId
+            Slog.d(TAG, "userActivityNoUpdateLocked: groupId=" + powerGroup.getGroupId()
                     + ", eventTime=" + eventTime + ", event=" + event
                     + ", flags=0x" + Integer.toHexString(flags) + ", uid=" + uid);
         }
@@ -1801,7 +1800,7 @@
                 mOverriddenTimeout = -1;
             }
 
-            final int wakefulness = mPowerGroups.get(groupId).getWakefulnessLocked();
+            final int wakefulness = powerGroup.getWakefulnessLocked();
             if (wakefulness == WAKEFULNESS_ASLEEP
                     || wakefulness == WAKEFULNESS_DOZING
                     || (flags & PowerManager.USER_ACTIVITY_FLAG_INDIRECT) != 0) {
@@ -1811,11 +1810,9 @@
             maybeUpdateForegroundProfileLastActivityLocked(eventTime);
 
             if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
-                if (eventTime
-                        > mPowerGroups.get(groupId).getLastUserActivityTimeNoChangeLightsLocked()
-                        && eventTime > mPowerGroups.get(groupId).getLastUserActivityTimeLocked()) {
-                    mPowerGroups.get(groupId).setLastUserActivityTimeNoChangeLightsLocked(
-                            eventTime);
+                if (eventTime > powerGroup.getLastUserActivityTimeNoChangeLightsLocked()
+                        && eventTime > powerGroup.getLastUserActivityTimeLocked()) {
+                    powerGroup.setLastUserActivityTimeNoChangeLightsLocked(eventTime);
                     mDirty |= DIRTY_USER_ACTIVITY;
                     if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                         mDirty |= DIRTY_QUIESCENT;
@@ -1824,8 +1821,8 @@
                     return true;
                 }
             } else {
-                if (eventTime > mPowerGroups.get(groupId).getLastUserActivityTimeLocked()) {
-                    mPowerGroups.get(groupId).setLastUserActivityTimeLocked(eventTime);
+                if (eventTime > powerGroup.getLastUserActivityTimeLocked()) {
+                    powerGroup.setLastUserActivityTimeLocked(eventTime);
                     mDirty |= DIRTY_USER_ACTIVITY;
                     if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                         mDirty |= DIRTY_QUIESCENT;
@@ -1850,16 +1847,17 @@
     private void wakeDisplayGroup(int groupId, long eventTime, @WakeReason int reason,
             String details, int uid, String opPackageName, int opUid) {
         synchronized (mLock) {
-            if (wakeDisplayGroupNoUpdateLocked(groupId, eventTime, reason, details, uid,
-                    opPackageName, opUid)) {
+            if (wakeDisplayGroupNoUpdateLocked(mPowerGroups.get(groupId), eventTime, reason,
+                    details, uid, opPackageName, opUid)) {
                 updatePowerStateLocked();
             }
         }
     }
 
     @GuardedBy("mLock")
-    private boolean wakeDisplayGroupNoUpdateLocked(int groupId, long eventTime,
+    private boolean wakeDisplayGroupNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
             @WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
+        final int groupId = powerGroup.getGroupId();
         if (DEBUG_SPEW) {
             Slog.d(TAG, "wakeDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                     + ", groupId=" + groupId + ", uid=" + uid);
@@ -1869,7 +1867,7 @@
             return false;
         }
 
-        final int currentState = mPowerGroups.get(groupId).getWakefulnessLocked();
+        final int currentState = powerGroup.getWakefulnessLocked();
         if (currentState == WAKEFULNESS_AWAKE) {
             if (!mBootCompleted && sQuiescent) {
                 mDirty |= DIRTY_QUIESCENT;
@@ -1892,9 +1890,8 @@
             LatencyTracker.getInstance(mContext)
                     .onActionStart(ACTION_TURN_ON_SCREEN, String.valueOf(groupId));
 
-            setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
+            setWakefulnessLocked(powerGroup, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
                     opPackageName, details);
-            PowerGroup powerGroup = mPowerGroups.get(groupId);
             powerGroup.setLastPowerOnTimeLocked(eventTime);
             powerGroup.setIsPoweringOnLocked(true);
         } finally {
@@ -1907,19 +1904,20 @@
     private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags,
             int uid) {
         synchronized (mLock) {
-            if (sleepDisplayGroupNoUpdateLocked(groupId, eventTime, reason, flags, uid)) {
+            if (sleepDisplayGroupNoUpdateLocked(mPowerGroups.get(groupId), eventTime, reason, flags,
+                    uid)) {
                 updatePowerStateLocked();
             }
         }
     }
 
     @GuardedBy("mLock")
-    private boolean sleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int reason,
-            int flags, int uid) {
+    private boolean sleepDisplayGroupNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
+            int reason, int flags, int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "sleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime
-                    + ", groupId=" + groupId + ", reason=" + reason + ", flags=" + flags
-                    + ", uid=" + uid);
+                    + ", groupId=" + powerGroup.getGroupId() + ", reason=" + reason
+                    + ", flags=" + flags + ", uid=" + uid);
         }
 
         if (eventTime < mLastWakeTime
@@ -1929,7 +1927,7 @@
             return false;
         }
 
-        final int wakefulness = mPowerGroups.get(groupId).getWakefulnessLocked();
+        final int wakefulness = powerGroup.getWakefulnessLocked();
         if (!PowerManagerInternal.isInteractive(wakefulness)) {
             return false;
         }
@@ -1939,14 +1937,14 @@
             reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
                     Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
             Slog.i(TAG, "Powering off display group due to "
-                    + PowerManager.sleepReasonToString(reason) + " (groupId= " + groupId
-                    + ", uid= " + uid + ")...");
+                    + PowerManager.sleepReasonToString(reason)
+                    + " (groupId= " + powerGroup.getGroupId() + ", uid= " + uid + ")...");
 
-            mPowerGroups.get(groupId).setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
-            setWakefulnessLocked(groupId, WAKEFULNESS_DOZING, eventTime, uid, reason,
+            powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
+            setWakefulnessLocked(powerGroup, WAKEFULNESS_DOZING, eventTime, uid, reason,
                     /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
             if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
-                reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid);
+                reallySleepDisplayGroupNoUpdateLocked(powerGroup, eventTime, uid);
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
@@ -1956,14 +1954,15 @@
 
     private void dreamDisplayGroup(int groupId, long eventTime, int uid) {
         synchronized (mLock) {
-            if (dreamDisplayGroupNoUpdateLocked(groupId, eventTime, uid)) {
+            if (dreamDisplayGroupNoUpdateLocked(mPowerGroups.get(groupId), eventTime, uid)) {
                 updatePowerStateLocked();
             }
         }
     }
 
     @GuardedBy("mLock")
-    private boolean dreamDisplayGroupNoUpdateLocked(int groupId, long eventTime, int uid) {
+    private boolean dreamDisplayGroupNoUpdateLocked(final PowerGroup powerGroup, long eventTime,
+            int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "dreamDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                     + ", uid=" + uid);
@@ -1976,11 +1975,12 @@
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "napDisplayGroup");
         try {
-            Slog.i(TAG, "Napping display group (groupId=" + groupId + ", uid=" + uid + ")...");
+            Slog.i(TAG, "Napping display group (groupId=" + powerGroup.getGroupId() + ", uid=" + uid
+                    + ")...");
 
-            mPowerGroups.get(groupId).setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
-            setWakefulnessLocked(groupId, WAKEFULNESS_DREAMING, eventTime, uid, /* reason= */
-                    0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
+            powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ true);
+            setWakefulnessLocked(powerGroup, WAKEFULNESS_DREAMING, eventTime, uid,
+                    /* reason= */0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
 
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
@@ -1989,7 +1989,8 @@
     }
 
     @GuardedBy("mLock")
-    private boolean reallySleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int uid) {
+    private boolean reallySleepDisplayGroupNoUpdateLocked(final PowerGroup powerGroup,
+            long eventTime, int uid) {
         if (DEBUG_SPEW) {
             Slog.d(TAG, "reallySleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                     + ", uid=" + uid);
@@ -1997,16 +1998,18 @@
 
         if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP
                 || !mBootCompleted || !mSystemReady
-                || mPowerGroups.get(groupId).getWakefulnessLocked()
+                || powerGroup.getWakefulnessLocked()
                 == WAKEFULNESS_ASLEEP) {
             return false;
         }
 
         Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallySleepDisplayGroup");
         try {
-            Slog.i(TAG, "Sleeping display group (groupId=" + groupId + ", uid=" + uid + ")...");
+            Slog.i(TAG,
+                    "Sleeping display group (groupId=" + powerGroup.getGroupId() + ", uid=" + uid
+                            + ")...");
 
-            setWakefulnessLocked(groupId, WAKEFULNESS_ASLEEP, eventTime, uid,
+            setWakefulnessLocked(powerGroup, WAKEFULNESS_ASLEEP, eventTime, uid,
                     PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,  /* opUid= */ 0,
                     /* opPackageName= */ null, /* details= */ null);
         } finally {
@@ -2019,14 +2022,21 @@
     @GuardedBy("mLock")
     void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason,
             int opUid, String opPackageName, String details) {
-        if (mPowerGroups.get(groupId).setWakefulnessLocked(wakefulness)) {
+        setWakefulnessLocked(mPowerGroups.get(groupId), wakefulness, eventTime, uid, reason, opUid,
+                opPackageName, details);
+    }
+
+    @GuardedBy("mLock")
+    private void setWakefulnessLocked(final PowerGroup powerGroup, int wakefulness, long eventTime,
+            int uid, int reason, int opUid, String opPackageName, String details) {
+        if (powerGroup.setWakefulnessLocked(wakefulness)) {
             mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
             setGlobalWakefulnessLocked(getGlobalWakefulnessLocked(),
                     eventTime, reason, uid, opUid, opPackageName, details);
             if (wakefulness == WAKEFULNESS_AWAKE) {
                 // Kick user activity to prevent newly awake group from timing out instantly.
-                userActivityNoUpdateLocked(
-                        groupId, eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
+                userActivityNoUpdateLocked(powerGroup, eventTime,
+                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
             }
         }
     }
@@ -2138,9 +2148,9 @@
     }
 
     /**
-     * Returns the amalgamated wakefulness of all {@link DisplayGroup DisplayGroups}.
+     * Returns the amalgamated wakefulness of all {@link PowerGroup PowerGroups}.
      *
-     * <p>This will be the highest wakeful state of all {@link DisplayGroup DisplayGroups}; ordered
+     * <p>This will be the highest wakeful state of all {@link PowerGroup PowerGroups}; ordered
      * from highest to lowest:
      * <ol>
      *     <li>{@link PowerManagerInternal#WAKEFULNESS_AWAKE}
@@ -2171,14 +2181,18 @@
     }
 
     @GuardedBy("mLock")
-    void onDisplayGroupEventLocked(int event, int groupId) {
+    void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
+        final int groupId = powerGroup.getGroupId();
+        if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
+            mPowerGroups.remove(groupId);
+        }
         final int oldWakefulness = getWakefulnessLocked();
         final int newWakefulness = getGlobalWakefulnessLocked();
 
         if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_ADDED
                 && newWakefulness == WAKEFULNESS_AWAKE) {
             // Kick user activity to prevent newly added group from timing out instantly.
-            userActivityNoUpdateLocked(groupId, mClock.uptimeMillis(),
+            userActivityNoUpdateLocked(powerGroup, mClock.uptimeMillis(),
                     PowerManager.USER_ACTIVITY_EVENT_OTHER, /* flags= */ 0, Process.SYSTEM_UID);
         }
 
@@ -2380,13 +2394,13 @@
                 final long now = mClock.uptimeMillis();
                 if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
                         dockedOnWirelessCharger)) {
-                    wakeDisplayGroupNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, now,
-                            PowerManager.WAKE_REASON_PLUGGED_IN,
+                    wakeDisplayGroupNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                            now, PowerManager.WAKE_REASON_PLUGGED_IN,
                             "android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
                 }
 
-                userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, now,
+                userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), now,
                         PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
 
                 // only play charging sounds if boot is completed so charging sounds don't play
@@ -2487,8 +2501,8 @@
                 mProfilePowerState.valueAt(i).mWakeLockSummary = 0;
             }
 
-            for (int groupId : mDisplayGroupIds) {
-                mPowerGroups.get(groupId).setWakeLockSummaryLocked(0);
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                mPowerGroups.valueAt(idx).setWakeLockSummaryLocked(0);
             }
 
             int invalidGroupWakeLockSummary = 0;
@@ -2498,17 +2512,18 @@
                 final Integer groupId = wakeLock.getDisplayGroupId();
                 // a wakelock with an invalid group ID should affect all groups
                 if (groupId == null || (groupId != Display.INVALID_DISPLAY_GROUP
-                        && !ArrayUtils.contains(mDisplayGroupIds, groupId))) {
+                        && !mPowerGroups.contains(groupId))) {
                     continue;
                 }
 
+                final PowerGroup powerGroup = mPowerGroups.get(groupId);
                 final int wakeLockFlags = getWakeLockSummaryFlags(wakeLock);
                 mWakeLockSummary |= wakeLockFlags;
 
                 if (groupId != Display.INVALID_DISPLAY_GROUP) {
-                    int wakeLockSummary = mPowerGroups.get(groupId).getWakeLockSummaryLocked();
+                    int wakeLockSummary = powerGroup.getWakeLockSummaryLocked();
                     wakeLockSummary |= wakeLockFlags;
-                    mPowerGroups.get(groupId).setWakeLockSummaryLocked(wakeLockSummary);
+                    powerGroup.setWakeLockSummaryLocked(wakeLockSummary);
                 } else {
                     invalidGroupWakeLockSummary |= wakeLockFlags;
                 }
@@ -2521,12 +2536,12 @@
                 }
             }
 
-            for (int groupId : mDisplayGroupIds) {
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
                 final int wakeLockSummary = adjustWakeLockSummary(
-                        mPowerGroups.get(groupId).getWakefulnessLocked(),
-                        invalidGroupWakeLockSummary
-                                | mPowerGroups.get(groupId).getWakeLockSummaryLocked());
-                mPowerGroups.get(groupId).setWakeLockSummaryLocked(wakeLockSummary);
+                        powerGroup.getWakefulnessLocked(),
+                        invalidGroupWakeLockSummary | powerGroup.getWakeLockSummaryLocked());
+                powerGroup.setWakeLockSummaryLocked(wakeLockSummary);
             }
 
             mWakeLockSummary = adjustWakeLockSummary(getWakefulnessLocked(),
@@ -2684,14 +2699,14 @@
         final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
         long nextTimeout = -1;
         boolean hasUserActivitySummary = false;
-        for (int groupId : mDisplayGroupIds) {
+        for (int idx = 0; idx < mPowerGroups.size(); idx++) {
             int groupUserActivitySummary = 0;
             long groupNextTimeout = 0;
-            if (mPowerGroups.get(groupId).getWakefulnessLocked() != WAKEFULNESS_ASLEEP) {
-                final long lastUserActivityTime =
-                        mPowerGroups.get(groupId).getLastUserActivityTimeLocked();
+            final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
+            if (powerGroup.getWakefulnessLocked() != WAKEFULNESS_ASLEEP) {
+                final long lastUserActivityTime = powerGroup.getLastUserActivityTimeLocked();
                 final long lastUserActivityTimeNoChangeLights =
-                        mPowerGroups.get(groupId).getLastUserActivityTimeNoChangeLightsLocked();
+                        powerGroup.getLastUserActivityTimeNoChangeLightsLocked();
                 if (lastUserActivityTime >= mLastWakeTime) {
                     groupNextTimeout = lastUserActivityTime + screenOffTimeout - screenDimDuration;
                     if (now < groupNextTimeout) {
@@ -2708,7 +2723,7 @@
                     groupNextTimeout = lastUserActivityTimeNoChangeLights + screenOffTimeout;
                     if (now < groupNextTimeout) {
                         final DisplayPowerRequest displayPowerRequest =
-                                mPowerGroups.get(groupId).getDisplayPowerRequestLocked();
+                                powerGroup.getDisplayPowerRequestLocked();
                         if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
                                 || displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
                             groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
@@ -2749,7 +2764,7 @@
                 }
 
                 if ((groupUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
-                        && (mPowerGroups.get(groupId).getWakeLockSummaryLocked()
+                        && (powerGroup.getWakeLockSummaryLocked()
                         & WAKE_LOCK_STAY_AWAKE) == 0) {
                     groupNextTimeout = mAttentionDetector.updateUserActivity(groupNextTimeout,
                             screenDimDuration);
@@ -2764,12 +2779,11 @@
                 }
             }
 
-            mPowerGroups.get(groupId).setUserActivitySummaryLocked(groupUserActivitySummary);
+            powerGroup.setUserActivitySummaryLocked(groupUserActivitySummary);
 
             if (DEBUG_SPEW) {
-                Slog.d(TAG, "updateUserActivitySummaryLocked: groupId=" + groupId
-                        + ", mWakefulness=" + wakefulnessToString(
-                        mPowerGroups.get(groupId).getWakefulnessLocked())
+                Slog.d(TAG, "updateUserActivitySummaryLocked: groupId=" + powerGroup.getGroupId()
+                        + ", mWakefulness=" + wakefulnessToString(powerGroup.getWakefulnessLocked())
                         + ", mUserActivitySummary=0x" + Integer.toHexString(
                         groupUserActivitySummary)
                         + ", nextTimeout=" + TimeUtils.formatUptime(groupNextTimeout));
@@ -2880,12 +2894,11 @@
     }
 
     @GuardedBy("mLock")
-    private boolean isAttentiveTimeoutExpired(int groupId, long now) {
+    private boolean isAttentiveTimeoutExpired(final PowerGroup powerGroup, long now) {
         long attentiveTimeout = getAttentiveTimeoutLocked();
         // Attentive state only applies to the default display group.
-        return groupId == Display.DEFAULT_DISPLAY_GROUP && attentiveTimeout >= 0
-                && now >= mPowerGroups.get(groupId).getLastUserActivityTimeLocked()
-                + attentiveTimeout;
+        return powerGroup.getGroupId() == Display.DEFAULT_DISPLAY_GROUP && attentiveTimeout >= 0
+                && now >= powerGroup.getLastUserActivityTimeLocked() + attentiveTimeout;
     }
 
     /**
@@ -2990,28 +3003,32 @@
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
                 | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
                 | DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
-                | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
-            final long time = mClock.uptimeMillis();
-            for (int id : mDisplayGroupIds) {
-                if (mPowerGroups.get(id).getWakefulnessLocked() == WAKEFULNESS_AWAKE
-                        && isItBedTimeYetLocked(id)) {
-                    if (DEBUG_SPEW) {
-                        Slog.d(TAG, "updateWakefulnessLocked: Bed time for group " + id);
-                    }
-                    if (isAttentiveTimeoutExpired(id, time)) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Going to sleep now due to long user inactivity");
-                        }
-                        changed = sleepDisplayGroupNoUpdateLocked(id, time,
-                                PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
-                                PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
-                    } else if (shouldNapAtBedTimeLocked()) {
-                        changed = dreamDisplayGroupNoUpdateLocked(id, time, Process.SYSTEM_UID);
-                    } else {
-                        changed = sleepDisplayGroupNoUpdateLocked(id, time,
-                                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
-                    }
+                | DIRTY_SCREEN_BRIGHTNESS_BOOST)) == 0) {
+            return changed;
+        }
+        final long time = mClock.uptimeMillis();
+        for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+            final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
+            if (!(powerGroup.getWakefulnessLocked() == WAKEFULNESS_AWAKE
+                    && isItBedTimeYetLocked(powerGroup))) {
+                continue;
+            }
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateWakefulnessLocked: Bed time for group "
+                        + powerGroup.getGroupId());
+            }
+            if (isAttentiveTimeoutExpired(powerGroup, time)) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Going to sleep now due to long user inactivity");
                 }
+                changed = sleepDisplayGroupNoUpdateLocked(powerGroup, time,
+                        PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
+                        PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+            } else if (shouldNapAtBedTimeLocked()) {
+                changed = dreamDisplayGroupNoUpdateLocked(powerGroup, time, Process.SYSTEM_UID);
+            } else {
+                changed = sleepDisplayGroupNoUpdateLocked(powerGroup, time,
+                        PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
             }
         }
         return changed;
@@ -3029,39 +3046,38 @@
     }
 
     /**
-     * Returns true if the DisplayGroup with the provided {@code groupId} should go to sleep now.
+     * Returns true if the provided {@link PowerGroup} should go to sleep now.
      * Also used when exiting a dream to determine whether we should go back to being fully awake or
      * else go to sleep for good.
      */
     @GuardedBy("mLock")
-    private boolean isItBedTimeYetLocked(int groupId) {
+    private boolean isItBedTimeYetLocked(PowerGroup powerGroup) {
         if (!mBootCompleted) {
             return false;
         }
 
         long now = mClock.uptimeMillis();
-        if (isAttentiveTimeoutExpired(groupId, now)) {
+        if (isAttentiveTimeoutExpired(powerGroup, now)) {
             return !isBeingKeptFromInattentiveSleepLocked();
         } else {
-            return !isBeingKeptAwakeLocked(groupId);
+            return !isBeingKeptAwakeLocked(powerGroup);
         }
     }
 
     /**
-     * Returns true if the DisplayGroup with the provided {@code groupId} is being kept awake by a
-     * wake lock, user activity or the stay on while powered setting.  We also keep the phone awake
-     * when the proximity sensor returns a positive result so that the device does not lock while in
-     * a phone call.  This function only controls whether the device will go to sleep or dream which
-     * is independent of whether it will be allowed to suspend.
+     * Returns true if the provided {@link PowerGroup} is being kept awake by a wake lock, user
+     * activity or the stay on while powered setting.  We also keep the phone awake when the
+     * proximity sensor returns a positive result so that the device does not lock while in a phone
+     * call. This function only controls whether the device will go to sleep or dream which is
+     * independent of whether it will be allowed to suspend.
      */
     @GuardedBy("mLock")
-    private boolean isBeingKeptAwakeLocked(int groupId) {
+    private boolean isBeingKeptAwakeLocked(final PowerGroup powerGroup) {
         return mStayOn
                 || mProximityPositive
-                || (mPowerGroups.get(groupId).getWakeLockSummaryLocked() & WAKE_LOCK_STAY_AWAKE)
-                != 0
-                || (mPowerGroups.get(groupId).getUserActivitySummaryLocked() & (
-                USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0
+                || (powerGroup.getWakeLockSummaryLocked() & WAKE_LOCK_STAY_AWAKE) != 0
+                || (powerGroup.getUserActivitySummaryLocked() & (
+                        USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM)) != 0
                 || mScreenBrightnessBoostInProgress;
     }
 
@@ -3102,10 +3118,11 @@
     private void scheduleSandmanLocked() {
         if (!mSandmanScheduled) {
             mSandmanScheduled = true;
-            for (int id : mDisplayGroupIds) {
-                if (mPowerGroups.get(id).supportsSandmanLocked()) {
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
+                if (powerGroup.supportsSandmanLocked()) {
                     Message msg = mHandler.obtainMessage(MSG_SANDMAN);
-                    msg.arg1 = id;
+                    msg.arg1 = powerGroup.getGroupId();
                     msg.setAsynchronous(true);
                     mHandler.sendMessage(msg);
                 }
@@ -3126,16 +3143,16 @@
         final int wakefulness;
         synchronized (mLock) {
             mSandmanScheduled = false;
-            if (!ArrayUtils.contains(mDisplayGroupIds, groupId)) {
+            if (!mPowerGroups.contains(groupId)) {
                 // Group has been removed.
                 return;
             }
-            wakefulness = mPowerGroups.get(groupId).getWakefulnessLocked();
+            final PowerGroup powerGroup = mPowerGroups.get(groupId);
+            wakefulness = powerGroup.getWakefulnessLocked();
             if ((wakefulness == WAKEFULNESS_DREAMING || wakefulness == WAKEFULNESS_DOZING) &&
-                    mPowerGroups.get(groupId).isSandmanSummonedLocked()
-                    && mPowerGroups.get(groupId).isReadyLocked()) {
-                startDreaming = canDreamLocked(groupId) || canDozeLocked();
-                mPowerGroups.get(groupId).setSandmanSummonedLocked(/* isSandmanSummoned= */ false);
+                    powerGroup.isSandmanSummonedLocked() && powerGroup.isReadyLocked()) {
+                startDreaming = canDreamLocked(powerGroup) || canDozeLocked();
+                powerGroup.setSandmanSummonedLocked(/* isSandmanSummoned= */ false);
             } else {
                 startDreaming = false;
             }
@@ -3162,7 +3179,7 @@
 
         // Update dream state.
         synchronized (mLock) {
-            if (!ArrayUtils.contains(mDisplayGroupIds, groupId)) {
+            if (!mPowerGroups.contains(groupId)) {
                 // Group has been removed.
                 return;
             }
@@ -3179,19 +3196,20 @@
 
             // If preconditions changed, wait for the next iteration to determine
             // whether the dream should continue (or be restarted).
-            if (mPowerGroups.get(groupId).isSandmanSummonedLocked()
-                    || mPowerGroups.get(groupId).getWakefulnessLocked() != wakefulness) {
+            final PowerGroup powerGroup = mPowerGroups.get(groupId);
+            if (powerGroup.isSandmanSummonedLocked()
+                    || powerGroup.getWakefulnessLocked() != wakefulness) {
                 return; // wait for next cycle
             }
 
             // Determine whether the dream should continue.
             long now = mClock.uptimeMillis();
             if (wakefulness == WAKEFULNESS_DREAMING) {
-                if (isDreaming && canDreamLocked(groupId)) {
+                if (isDreaming && canDreamLocked(powerGroup)) {
                     if (mDreamsBatteryLevelDrainCutoffConfig >= 0
                             && mBatteryLevel < mBatteryLevelWhenDreamStarted
                                     - mDreamsBatteryLevelDrainCutoffConfig
-                            && !isBeingKeptAwakeLocked(groupId)) {
+                            && !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.
@@ -3206,13 +3224,14 @@
                 }
 
                 // Dream has ended or will be stopped.  Update the power state.
-                if (isItBedTimeYetLocked(groupId)) {
-                    final int flags = isAttentiveTimeoutExpired(groupId, now)
+                if (isItBedTimeYetLocked(powerGroup)) {
+                    final int flags = isAttentiveTimeoutExpired(powerGroup, now)
                             ? PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE : 0;
-                    sleepDisplayGroupNoUpdateLocked(groupId, now,
+                    sleepDisplayGroupNoUpdateLocked(powerGroup, now,
                             PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, Process.SYSTEM_UID);
                 } else {
-                    wakeDisplayGroupNoUpdateLocked(groupId, now, PowerManager.WAKE_REASON_UNKNOWN,
+                    wakeDisplayGroupNoUpdateLocked(powerGroup, now,
+                            PowerManager.WAKE_REASON_UNKNOWN,
                             "android.server.power:DREAM_FINISHED", Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
                 }
@@ -3223,7 +3242,7 @@
                 }
 
                 // Doze has ended or will be stopped.  Update the power state.
-                reallySleepDisplayGroupNoUpdateLocked(groupId, now, Process.SYSTEM_UID);
+                reallySleepDisplayGroupNoUpdateLocked(powerGroup, now, Process.SYSTEM_UID);
                 updatePowerStateLocked();
             }
         }
@@ -3238,21 +3257,19 @@
      * Returns true if the {@code groupId} is allowed to dream in its current state.
      */
     @GuardedBy("mLock")
-    private boolean canDreamLocked(int groupId) {
-        final DisplayPowerRequest displayPowerRequest =
-                mPowerGroups.get(groupId).getDisplayPowerRequestLocked();
+    private boolean canDreamLocked(final PowerGroup powerGroup) {
+        final DisplayPowerRequest displayPowerRequest = powerGroup.getDisplayPowerRequestLocked();
         if (!mBootCompleted
                 || getWakefulnessLocked() != WAKEFULNESS_DREAMING
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
                 || !displayPowerRequest.isBrightOrDim()
                 || displayPowerRequest.isVr()
-                || (mPowerGroups.get(groupId).getUserActivitySummaryLocked() & (
-                USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM
-                        | USER_ACTIVITY_SCREEN_DREAM)) == 0) {
+                || (powerGroup.getUserActivitySummaryLocked() & (USER_ACTIVITY_SCREEN_BRIGHT
+                | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0) {
             return false;
         }
-        if (!isBeingKeptAwakeLocked(groupId)) {
+        if (!isBeingKeptAwakeLocked(powerGroup)) {
             if (!mIsPowered && !mDreamsEnabledOnBatteryConfig) {
                 return false;
             }
@@ -3302,10 +3319,12 @@
                 }
             }
 
-            for (final int groupId : mDisplayGroupIds) {
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
+                final int groupId = powerGroup.getGroupId();
                 final DisplayPowerRequest displayPowerRequest =
-                        mPowerGroups.get(groupId).getDisplayPowerRequestLocked();
-                displayPowerRequest.policy = getDesiredScreenPolicyLocked(groupId);
+                        powerGroup.getDisplayPowerRequestLocked();
+                displayPowerRequest.policy = getDesiredScreenPolicyLocked(powerGroup);
 
                 // Determine appropriate screen brightness and auto-brightness adjustments.
                 final boolean autoBrightness;
@@ -3334,7 +3353,7 @@
 
                 if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
                     displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
-                    if ((mPowerGroups.get(groupId).getWakeLockSummaryLocked() & WAKE_LOCK_DRAW) != 0
+                    if ((powerGroup.getWakeLockSummaryLocked() & WAKE_LOCK_DRAW) != 0
                             && !mDrawWakeLockOverrideFromSidekick) {
                         if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
                             displayPowerRequest.dozeScreenState = Display.STATE_DOZE;
@@ -3361,11 +3380,11 @@
                             + ", policy=" + policyToString(displayPowerRequest.policy)
                             + ", mWakefulness="
                             + PowerManagerInternal.wakefulnessToString(
-                            mPowerGroups.get(groupId).getWakefulnessLocked())
+                            powerGroup.getWakefulnessLocked())
                             + ", mWakeLockSummary=0x" + Integer.toHexString(
-                            mPowerGroups.get(groupId).getWakeLockSummaryLocked())
+                            powerGroup.getWakeLockSummaryLocked())
                             + ", mUserActivitySummary=0x" + Integer.toHexString(
-                            mPowerGroups.get(groupId).getUserActivitySummaryLocked())
+                            powerGroup.getUserActivitySummaryLocked())
                             + ", mBootCompleted=" + mBootCompleted
                             + ", screenBrightnessOverride="
                             + displayPowerRequest.screenBrightnessOverride
@@ -3376,16 +3395,15 @@
                             + ", sQuiescent=" + sQuiescent);
                 }
 
-                final boolean displayReadyStateChanged =
-                        mPowerGroups.get(groupId).setReadyLocked(ready);
-                final boolean poweringOn = mPowerGroups.get(groupId).isPoweringOnLocked();
+                final boolean displayReadyStateChanged = powerGroup.setReadyLocked(ready);
+                final boolean poweringOn = powerGroup.isPoweringOnLocked();
                 if (ready && displayReadyStateChanged && poweringOn
-                        && mPowerGroups.get(groupId).getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
-                    mPowerGroups.get(groupId).setIsPoweringOnLocked(false);
+                        && powerGroup.getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
+                    powerGroup.setIsPoweringOnLocked(false);
                     LatencyTracker.getInstance(mContext).onActionEnd(ACTION_TURN_ON_SCREEN);
                     Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
                     final int latencyMs = (int) (mClock.uptimeMillis()
-                            - mPowerGroups.get(groupId).getLastPowerOnTimeLocked());
+                            - powerGroup.getLastPowerOnTimeLocked());
                     if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) {
                         Slog.w(TAG, "Screen on took " + latencyMs + " ms");
                     }
@@ -3431,8 +3449,12 @@
     @VisibleForTesting
     @GuardedBy("mLock")
     int getDesiredScreenPolicyLocked(int groupId) {
-        final int wakefulness = mPowerGroups.get(groupId).getWakefulnessLocked();
-        final int wakeLockSummary = mPowerGroups.get(groupId).getWakeLockSummaryLocked();
+        return getDesiredScreenPolicyLocked(mPowerGroups.get(groupId));
+    }
+
+    int getDesiredScreenPolicyLocked(final PowerGroup powerGroup) {
+        final int wakefulness = powerGroup.getWakefulnessLocked();
+        final int wakeLockSummary = powerGroup.getWakeLockSummaryLocked();
         if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
             return DisplayPowerRequest.POLICY_OFF;
         } else if (wakefulness == WAKEFULNESS_DOZING) {
@@ -3455,8 +3477,7 @@
 
         if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
                 || !mBootCompleted
-                || (mPowerGroups.get(groupId).getUserActivitySummaryLocked()
-                & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+                || (powerGroup.getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
                 || mScreenBrightnessBoostInProgress) {
             return DisplayPowerRequest.POLICY_BRIGHT;
         }
@@ -3490,8 +3511,9 @@
                 mProximityPositive = false;
                 mInterceptedPowerKeyForProximity = false;
                 mDirty |= DIRTY_PROXIMITY_POSITIVE;
-                userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, mClock.uptimeMillis(),
-                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+                userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP),
+                        mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER,
+                        0 /* flags */, Process.SYSTEM_UID);
                 updatePowerStateLocked();
             }
         }
@@ -3551,8 +3573,8 @@
         final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
         final boolean autoSuspend = !needDisplaySuspendBlocker;
         boolean interactive = false;
-        for (int id : mDisplayGroupIds) {
-            interactive |= mPowerGroups.get(id).getDisplayPowerRequestLocked().isBrightOrDim();
+        for (int idx = 0; idx < mPowerGroups.size() && !interactive; idx++) {
+            interactive = mPowerGroups.valueAt(idx).getDisplayPowerRequestLocked().isBrightOrDim();
         }
 
         // Disable auto-suspend if needed.
@@ -3635,9 +3657,9 @@
             return true;
         }
 
-        for (int id : mDisplayGroupIds) {
+        for (int idx = 0; idx < mPowerGroups.size(); idx++) {
             final DisplayPowerRequest displayPowerRequest =
-                    mPowerGroups.get(id).getDisplayPowerRequestLocked();
+                    mPowerGroups.valueAt(idx).getDisplayPowerRequestLocked();
             if (displayPowerRequest.isBrightOrDim()) {
                 // If we asked for the screen to be on but it is off due to the proximity
                 // sensor then we may suspend but only if the configuration allows it.
@@ -4077,7 +4099,7 @@
             mScreenBrightnessBoostInProgress = true;
             mDirty |= DIRTY_SCREEN_BRIGHTNESS_BOOST;
 
-            userActivityNoUpdateLocked(Display.DEFAULT_DISPLAY_GROUP, eventTime,
+            userActivityNoUpdateLocked(mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP), eventTime,
                     PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
             updatePowerStateLocked();
         }
@@ -4201,9 +4223,9 @@
                 mForceSuspendActive = true;
                 // Place the system in an non-interactive state
                 boolean updatePowerState = false;
-                for (int id : mDisplayGroupIds) {
-                    updatePowerState |= sleepDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(),
-                            PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
+                for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                    updatePowerState |= sleepDisplayGroupNoUpdateLocked(mPowerGroups.valueAt(idx),
+                            mClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid);
                 }
                 if (updatePowerState) {
@@ -4504,16 +4526,17 @@
             }
 
             pw.println("Display Group User Activity:");
-            for (int id : mDisplayGroupIds) {
-                pw.println("  displayGroupId=" + id);
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
+                pw.println("  displayGroupId=" + powerGroup.getGroupId());
                 pw.println("  userActivitySummary=0x" + Integer.toHexString(
-                        mPowerGroups.get(id).getUserActivitySummaryLocked()));
+                        powerGroup.getUserActivitySummaryLocked()));
                 pw.println("  lastUserActivityTime=" + TimeUtils.formatUptime(
-                        mPowerGroups.get(id).getLastUserActivityTimeLocked()));
+                        powerGroup.getLastUserActivityTimeLocked()));
                 pw.println("  lastUserActivityTimeNoChangeLights=" + TimeUtils.formatUptime(
-                        mPowerGroups.get(id).getLastUserActivityTimeNoChangeLightsLocked()));
+                        powerGroup.getLastUserActivityTimeNoChangeLightsLocked()));
                 pw.println("  mWakeLockSummary=0x" + Integer.toHexString(
-                        mPowerGroups.get(id).getWakeLockSummaryLocked()));
+                        powerGroup.getWakeLockSummaryLocked()));
             }
 
             wcd = mWirelessChargerDetector;
@@ -4601,12 +4624,13 @@
             proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_DISPATCHED_MS, mNotifyLongDispatched);
             proto.write(PowerManagerServiceDumpProto.NOTIFY_LONG_NEXT_CHECK_MS, mNotifyLongNextCheck);
 
-            for (int id : mDisplayGroupIds) {
+            for (int idx = 0; idx < mPowerGroups.size(); idx++) {
+                final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
                 final long userActivityToken = proto.start(
                         PowerManagerServiceDumpProto.USER_ACTIVITY);
-                proto.write(PowerManagerServiceDumpProto.UserActivityProto.DISPLAY_GROUP_ID, id);
-                final long userActivitySummary =
-                        mPowerGroups.get(id).getUserActivitySummaryLocked();
+                proto.write(PowerManagerServiceDumpProto.UserActivityProto.DISPLAY_GROUP_ID,
+                        powerGroup.getGroupId());
+                final long userActivitySummary = powerGroup.getUserActivitySummaryLocked();
                 proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_BRIGHT,
                         (userActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0);
                 proto.write(PowerManagerServiceDumpProto.UserActivityProto.IS_SCREEN_DIM,
@@ -4615,10 +4639,10 @@
                         (userActivitySummary & USER_ACTIVITY_SCREEN_DREAM) != 0);
                 proto.write(
                         PowerManagerServiceDumpProto.UserActivityProto.LAST_USER_ACTIVITY_TIME_MS,
-                        mPowerGroups.get(id).getLastUserActivityTimeLocked());
+                        powerGroup.getLastUserActivityTimeLocked());
                 proto.write(
                         PowerManagerServiceDumpProto.UserActivityProto.LAST_USER_ACTIVITY_TIME_NO_CHANGE_LIGHTS_MS,
-                        mPowerGroups.get(id).getLastUserActivityTimeNoChangeLightsLocked());
+                        powerGroup.getLastUserActivityTimeNoChangeLightsLocked());
                 proto.end(userActivityToken);
             }
 
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
index 48d8fed..045b06c 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerPerUserService.java
@@ -42,6 +42,8 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.server.infra.AbstractPerUserSystemService;
 
+import java.io.PrintWriter;
+
 /**
  * Manages the Rotation Resolver Service on a per-user basis.
  */
@@ -216,6 +218,13 @@
         mCurrentRequest = null;
     }
 
+    @Override
+    @GuardedBy("mLock")
+    protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
+        super.dumpLocked(prefix, pw);
+        dumpInternal(new IndentingPrintWriter(pw, "  "));
+    }
+
     void dumpInternal(IndentingPrintWriter ipw) {
         synchronized (mLock) {
             if (mRemoteService != null) {
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
index 19a246e..8f603fc 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java
@@ -30,6 +30,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 import android.os.Binder;
@@ -42,7 +43,6 @@
 import android.service.rotationresolver.RotationResolutionRequest;
 import android.service.rotationresolver.RotationResolverService;
 import android.text.TextUtils;
-import android.util.IndentingPrintWriter;
 import android.util.Slog;
 import android.view.Surface;
 
@@ -143,6 +143,30 @@
         return !TextUtils.isEmpty(getServiceConfigPackage(context));
     }
 
+    ComponentName getComponentNameShellCommand(@UserIdInt int userId) {
+        synchronized (mLock) {
+            final RotationResolverManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                return service.getComponentName();
+            }
+        }
+        return null;
+    }
+
+    void resolveRotationShellCommand(@UserIdInt int userId,
+            RotationResolverInternal.RotationResolverCallbackInternal callbackInternal,
+            RotationResolutionRequest request) {
+        synchronized (mLock) {
+            final RotationResolverManagerPerUserService service = getServiceForUserLocked(userId);
+            if (service != null) {
+                service.resolveRotationLocked(callbackInternal, request, new CancellationSignal());
+            } else {
+                Slog.i(TAG, "service not available for user_id: " + userId);
+            }
+        }
+    }
+
+
     static String getServiceConfigPackage(Context context) {
         return context.getPackageManager().getRotationResolverPackageName();
     }
@@ -201,9 +225,7 @@
                 return;
             }
             synchronized (mLock) {
-                final RotationResolverManagerPerUserService service = getServiceForUserLocked(
-                        UserHandle.getCallingUserId());
-                service.dumpInternal(new IndentingPrintWriter(pw, "  "));
+                dumpLocked("", pw);
             }
         }
 
@@ -214,9 +236,8 @@
                 ResultReceiver resultReceiver) {
             mContext.enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROTATION_RESOLVER,
                     TAG);
-            final RotationResolverManagerPerUserService service = getServiceForUserLocked(
-                    UserHandle.getCallingUserId());
-            new RotationResolverShellCommand(service).exec(this, in, out, err, args, callback,
+            new RotationResolverShellCommand(RotationResolverManagerService.this).exec(this, in,
+                    out, err, args, callback,
                     resultReceiver);
         }
     }
diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
index 9f3f096..e450dd3 100644
--- a/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
+++ b/services/core/java/com/android/server/rotationresolver/RotationResolverShellCommand.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
-import android.os.CancellationSignal;
 import android.os.ShellCommand;
 import android.rotationresolver.RotationResolverInternal.RotationResolverCallbackInternal;
 import android.service.rotationresolver.RotationResolutionRequest;
@@ -31,9 +30,9 @@
     private static final int INITIAL_RESULT_CODE = -1;
 
     @NonNull
-    private final RotationResolverManagerPerUserService mService;
+    private final RotationResolverManagerService mService;
 
-    RotationResolverShellCommand(@NonNull RotationResolverManagerPerUserService service) {
+    RotationResolverShellCommand(@NonNull RotationResolverManagerService service) {
         mService = service;
     }
 
@@ -77,7 +76,7 @@
             case "get-bound-package":
                 return getBoundPackageName();
             case "set-temporary-service":
-                return setTemporaryService(getNextArgRequired());
+                return setTemporaryService();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -85,31 +84,33 @@
 
     private int getBoundPackageName() {
         final PrintWriter out = getOutPrintWriter();
-        final ComponentName componentName = mService.getComponentName();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final ComponentName componentName = mService.getComponentNameShellCommand(userId);
         out.println(componentName == null ? "" : componentName.getPackageName());
         return 0;
     }
 
-    private int setTemporaryService(String serviceName) {
+    private int setTemporaryService() {
         final PrintWriter out = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String serviceName = getNextArg();
         if (serviceName == null) {
-            mService.getMaster().resetTemporaryService(mService.getUserId());
+            mService.resetTemporaryService(userId);
             out.println("RotationResolverService temporary reset. ");
             return 0;
         }
-
         final int duration = Integer.parseInt(getNextArgRequired());
-        mService.getMaster().setTemporaryService(mService.getUserId(), serviceName, duration);
+        mService.setTemporaryService(userId, serviceName, duration);
         out.println("RotationResolverService temporarily set to " + serviceName
                 + " for " + duration + "ms");
         return 0;
     }
 
     private int runResolveRotation() {
+        final int userId = Integer.parseInt(getNextArgRequired());
         final RotationResolutionRequest request = new RotationResolutionRequest("",
-                Surface.ROTATION_0, Surface.ROTATION_0,  true, 2000L);
-        mService.resolveRotationLocked(sTestableRotationCallbackInternal, request,
-                new CancellationSignal());
+                Surface.ROTATION_0, Surface.ROTATION_0, true, 2000L);
+        mService.resolveRotationShellCommand(userId, sTestableRotationCallbackInternal, request);
         return 0;
     }
 
@@ -126,11 +127,12 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println();
-        pw.println("  resolve-rotation: request a rotation resolution.");
+        pw.println("  resolve-rotation USER_ID: request a rotation resolution.");
         pw.println("  get-last-resolution: show the last rotation resolution result.");
-        pw.println("  get-bound-package: print the bound package that implements the service.");
-        pw.println("  set-temporary-service [COMPONENT_NAME DURATION]");
+        pw.println("  get-bound-package USER_ID:");
+        pw.println("    Print the bound package that implements the service.");
+        pw.println("  set-temporary-service USER_ID [COMPONENT_NAME DURATION]");
         pw.println("    Temporarily (for DURATION ms) changes the service implementation.");
-        pw.println("    To reset, call with no argument.");
+        pw.println("    To reset, call with just the USER_ID argument.");
     }
 }
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index eb69ff7..b1cc517 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -51,6 +51,10 @@
 import static android.util.MathUtils.constrain;
 
 import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC;
 import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO;
@@ -168,6 +172,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.util.StatsEvent;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -179,7 +184,7 @@
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 import com.android.internal.os.BinderCallsStats.ExportedCallStat;
-import com.android.internal.os.DmabufInfoReader;
+import com.android.internal.os.KernelAllocationStats;
 import com.android.internal.os.KernelCpuBpfTracking;
 import com.android.internal.os.KernelCpuThreadReader;
 import com.android.internal.os.KernelCpuThreadReaderDiff;
@@ -421,7 +426,6 @@
     private final Object mSystemUptimeLock = new Object();
     private final Object mProcessMemoryStateLock = new Object();
     private final Object mProcessMemoryHighWaterMarkLock = new Object();
-    private final Object mProcessMemorySnapshotLock = new Object();
     private final Object mSystemIonHeapSizeLock = new Object();
     private final Object mIonHeapSizeLock = new Object();
     private final Object mProcessSystemIonHeapSizeLock = new Object();
@@ -559,9 +563,7 @@
                             return pullProcessMemoryHighWaterMarkLocked(atomTag, data);
                         }
                     case FrameworkStatsLog.PROCESS_MEMORY_SNAPSHOT:
-                        synchronized (mProcessMemorySnapshotLock) {
-                            return pullProcessMemorySnapshotLocked(atomTag, data);
-                        }
+                        return pullProcessMemorySnapshot(atomTag, data);
                     case FrameworkStatsLog.SYSTEM_ION_HEAP_SIZE:
                         synchronized (mSystemIonHeapSizeLock) {
                             return pullSystemIonHeapSizeLocked(atomTag, data);
@@ -2214,10 +2216,16 @@
         );
     }
 
-    int pullProcessMemorySnapshotLocked(int atomTag, List<StatsEvent> pulledData) {
+    int pullProcessMemorySnapshot(int atomTag, List<StatsEvent> pulledData) {
         List<ProcessMemoryState> managedProcessList =
                 LocalServices.getService(ActivityManagerInternal.class)
                         .getMemoryStateForProcesses();
+        KernelAllocationStats.ProcessGpuMem[] gpuAllocations =
+                KernelAllocationStats.getGpuAllocations();
+        SparseIntArray gpuMemPerPid = new SparseIntArray(gpuAllocations.length);
+        for (KernelAllocationStats.ProcessGpuMem processGpuMem : gpuAllocations) {
+            gpuMemPerPid.put(processGpuMem.pid, processGpuMem.gpuMemoryKb);
+        }
         for (ProcessMemoryState managedProcess : managedProcessList) {
             final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid);
             if (snapshot == null) {
@@ -2226,7 +2234,8 @@
             pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, managedProcess.uid,
                     managedProcess.processName, managedProcess.pid, managedProcess.oomScore,
                     snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes,
-                    snapshot.anonRssInKilobytes + snapshot.swapInKilobytes));
+                    snapshot.anonRssInKilobytes + snapshot.swapInKilobytes,
+                    gpuMemPerPid.get(managedProcess.pid)));
         }
         // Complement the data with native system processes. Given these measurements can be taken
         // in response to LMKs happening, we want to first collect the managed app stats (to
@@ -2244,7 +2253,8 @@
                     processCmdlines.valueAt(i), pid,
                     -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/,
                     snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes,
-                    snapshot.anonRssInKilobytes + snapshot.swapInKilobytes));
+                    snapshot.anonRssInKilobytes + snapshot.swapInKilobytes,
+                    gpuMemPerPid.get(pid)));
         }
         return StatsManager.PULL_SUCCESS;
     }
@@ -2324,7 +2334,8 @@
             if (process.uid == Process.SYSTEM_UID) {
                 continue;
             }
-            DmabufInfoReader.ProcessDmabuf proc = DmabufInfoReader.getProcessStats(process.pid);
+            KernelAllocationStats.ProcessDmabuf proc =
+                    KernelAllocationStats.getDmabufAllocations(process.pid);
             if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) {
                 continue;
             }
@@ -4449,8 +4460,9 @@
                 final int userId = userInfo.getUserHandle().getIdentifier();
 
                 if (isAccessibilityShortcutUser(mContext, userId)) {
-                    final int software_shortcut_type = Settings.Secure.getIntForUser(resolver,
-                            Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId);
+                    final int software_shortcut_type = convertToAccessibilityShortcutType(
+                            Settings.Secure.getIntForUser(resolver,
+                                    Settings.Secure.ACCESSIBILITY_BUTTON_MODE, 0, userId));
                     final String software_shortcut_list = Settings.Secure.getStringForUser(resolver,
                             Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
                     final int software_shortcut_service_num = countAccessibilityServices(
@@ -4727,6 +4739,19 @@
                 && !TextUtils.isEmpty(software_string);
     }
 
+    private int convertToAccessibilityShortcutType(int shortcutType) {
+        switch (shortcutType) {
+            case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON;
+            case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU;
+            case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE;
+            default:
+                return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
+        }
+    }
+
     // Thermal event received from vendor thermal management subsystem
     private static final class ThermalEventListener extends IThermalEventListener.Stub {
         @Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 232ea09..4eae939 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -64,6 +64,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -139,6 +140,8 @@
     private int mCurrentUserId;
     private boolean mTracingEnabled;
 
+    private final TileRequestTracker mTileRequestTracker;
+
     private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
     @GuardedBy("mLock")
     private IUdfpsHbmListener mUdfpsHbmListener;
@@ -245,6 +248,8 @@
         mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+        mTileRequestTracker = new TileRequestTracker(mContext);
     }
 
     @Override
@@ -1765,11 +1770,26 @@
             mCurrentRequestAddTilePackages.put(packageName, currentTime);
         }
 
+        if (mTileRequestTracker.shouldBeDenied(userId, componentName)) {
+            if (clearTileAddRequest(packageName)) {
+                try {
+                    callback.onTileRequest(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "requestAddTile - callback", e);
+                }
+            }
+            return;
+        }
+
         IAddTileResultCallback proxyCallback = new IAddTileResultCallback.Stub() {
             @Override
             public void onTileRequest(int i) {
                 if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED) {
                     i = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED;
+                } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED) {
+                    mTileRequestTracker.addDenial(userId, componentName);
+                } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED) {
+                    mTileRequestTracker.resetRequests(userId, componentName);
                 }
                 if (clearTileAddRequest(packageName)) {
                     try {
@@ -1961,6 +1981,8 @@
                 pw.println("    " + requests.get(i) + ",");
             }
             pw.println("  ]");
+            IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+            mTileRequestTracker.dump(fd, ipw.increaseIndent(), args);
         }
     }
 
diff --git a/services/core/java/com/android/server/statusbar/TileRequestTracker.java b/services/core/java/com/android/server/statusbar/TileRequestTracker.java
new file mode 100644
index 0000000..d5ace3f7
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/TileRequestTracker.java
@@ -0,0 +1,138 @@
+/*
+ * 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.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+
+/**
+ * Tracks user denials of requests from {@link StatusBarManagerService#requestAddTile}.
+ *
+ * After a certain number of denials for a particular pair (user,ComponentName), requests will be
+ * auto-denied without showing a dialog to the user.
+ */
+public class TileRequestTracker {
+
+    @VisibleForTesting
+    static final int MAX_NUM_DENIALS = 3;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final SparseArrayMap<ComponentName, Integer> mTrackingMap = new SparseArrayMap<>();
+    @GuardedBy("mLock")
+    private final ArraySet<ComponentName> mComponentsToRemove = new ArraySet<>();
+
+    private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                return;
+            }
+
+            Uri data = intent.getData();
+            String packageName = data.getEncodedSchemeSpecificPart();
+
+            if (!intent.hasExtra(Intent.EXTRA_UID)) {
+                return;
+            }
+            int userId = UserHandle.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
+            synchronized (mLock) {
+                mComponentsToRemove.clear();
+                final int elementsForUser = mTrackingMap.numElementsForKey(userId);
+                final int userKeyIndex = mTrackingMap.indexOfKey(userId);
+                for (int compKeyIndex = 0; compKeyIndex < elementsForUser; compKeyIndex++) {
+                    ComponentName c = mTrackingMap.keyAt(userKeyIndex, compKeyIndex);
+                    if (c.getPackageName().equals(packageName)) {
+                        mComponentsToRemove.add(c);
+                    }
+                }
+                final int compsToRemoveNum = mComponentsToRemove.size();
+                for (int i = 0; i < compsToRemoveNum; i++) {
+                    ComponentName c = mComponentsToRemove.valueAt(i);
+                    mTrackingMap.delete(userId, c);
+                }
+            }
+        }
+    };
+
+    TileRequestTracker(Context context) {
+        mContext = context;
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiverAsUser(mUninstallReceiver, UserHandle.ALL, intentFilter, null,
+                null);
+    }
+
+    /**
+     * Return whether this combination of {@code userId} and {@link ComponentName} should be
+     * auto-denied.
+     */
+    boolean shouldBeDenied(int userId, ComponentName componentName) {
+        synchronized (mLock) {
+            return mTrackingMap.getOrDefault(userId, componentName, 0) >= MAX_NUM_DENIALS;
+        }
+    }
+
+    /**
+     * Add a new denial instance for a given {@code userId} and {@link ComponentName}.
+     */
+    void addDenial(int userId, ComponentName componentName) {
+        synchronized (mLock) {
+            int current = mTrackingMap.getOrDefault(userId, componentName, 0);
+            mTrackingMap.add(userId, componentName, current + 1);
+        }
+    }
+
+    /**
+     * Reset the number of denied request for a given {@code userId} and {@link ComponentName}.
+     */
+    void resetRequests(int userId, ComponentName componentName) {
+        synchronized (mLock) {
+            mTrackingMap.delete(userId, componentName);
+        }
+    }
+
+    void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+        pw.println("TileRequestTracker:");
+        pw.increaseIndent();
+        synchronized (mLock) {
+            mTrackingMap.forEach((user, componentName, value) -> {
+                pw.println("user=" + user + ", " + componentName.toShortString() + ": " + value);
+            });
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index d24a3df..417177f 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -35,6 +35,7 @@
 import java.time.DateTimeException;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -55,6 +56,7 @@
      */
     @StringDef(prefix = "KEY_", value = {
             KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+            KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
             KEY_PRIMARY_LTZP_MODE_OVERRIDE,
             KEY_SECONDARY_LTZP_MODE_OVERRIDE,
             KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
@@ -66,6 +68,7 @@
             KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
             KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
             KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
+            KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
     })
     @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
     @Retention(RetentionPolicy.SOURCE)
@@ -81,6 +84,15 @@
             "location_time_zone_detection_feature_supported";
 
     /**
+     * Controls whether location time zone detection should run all the time on supported devices,
+     * even when the user has not enabled it explicitly in settings. Enabled for internal testing
+     * only.
+     */
+    public static final @DeviceConfigKey String
+            KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED =
+            "location_time_zone_detection_run_in_background_enabled";
+
+    /**
      * The key for the server flag that can override the device config for whether the primary
      * location time zone provider is enabled, disabled, or (for testing) in simulation mode.
      */
@@ -156,14 +168,25 @@
             "time_detector_origin_priorities_override";
 
     /**
-     * The key to override the time detector lower bound configuration. The values is the number of
+     * The key to override the time detector lower bound configuration. The value is the number of
      * milliseconds since the beginning of the Unix epoch.
      */
     public static final @DeviceConfigKey String KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE =
             "time_detector_lower_bound_millis_override";
 
+    /**
+     * The key to allow extra metrics / telemetry information to be collected from internal testers.
+     */
+    public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED =
+            "enhanced_metrics_collection_enabled";
+
+    /**
+     * The registered listeners and the keys to trigger on. The value is explicitly a HashSet to
+     * ensure O(1) lookup performance when working out whether a listener should trigger.
+     */
     @GuardedBy("mListeners")
-    private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();
+    private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
+            new ArrayMap<>();
 
     private static final Object SLOCK = new Object();
 
@@ -190,18 +213,29 @@
 
     private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
         synchronized (mListeners) {
-            for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry
+            for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
                     : mListeners.entrySet()) {
-                if (intersects(listenerEntry.getValue(), properties.getKeyset())) {
+                // 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
+                // only changed keys are reported. Because we guarantee the type / lookup behavior
+                // of the monitoredKeys by making that a HashSet, that is used as the haystack Set,
+                // while the changed keys is treated as the needles Iterable. At the time of
+                // writing, properties.getKeyset() actually returns a HashSet, so iteration isn't
+                // super efficient and the use of HashSet for monitoredKeys may be redundant, but
+                // neither set will be enormous.
+                HashSet<String> monitoredKeys = listenerEntry.getValue();
+                Iterable<String> modifiedKeys = properties.getKeyset();
+                if (containsAny(monitoredKeys, modifiedKeys)) {
                     listenerEntry.getKey().onChange();
                 }
             }
         }
     }
 
-    private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) {
-        for (String toFind : one) {
-            if (two.contains(toFind)) {
+    private static boolean containsAny(
+            @NonNull Set<String> haystack, @NonNull Iterable<String> needles) {
+        for (String needle : needles) {
+            if (haystack.contains(needle)) {
                 return true;
             }
         }
@@ -220,8 +254,11 @@
         Objects.requireNonNull(listener);
         Objects.requireNonNull(keys);
 
+        // Make a defensive copy and use a well-defined Set implementation to provide predictable
+        // performance on the lookup.
+        HashSet<String> keysCopy = new HashSet<>(keys);
         synchronized (mListeners) {
-            mListeners.put(listener, keys);
+            mListeners.put(listener, keysCopy);
         }
     }
 
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index 7f7d01c..5f14000 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.SystemProperties;
-import android.util.ArraySet;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -35,7 +34,6 @@
 
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -65,11 +63,10 @@
             Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
 
     /** Device config keys that affect the {@link TimeDetectorService}. */
-    private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
-            new ArraySet<>(new String[] {
-                    KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
-                    KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
-            }));
+    private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+            KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
+            KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE
+    );
 
     private static final Object SLOCK = new Object();
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 65f077e..ef4e42a 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -21,6 +21,7 @@
 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
@@ -28,6 +29,10 @@
 import android.app.time.TimeZoneConfiguration;
 import android.os.UserHandle;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.Objects;
 
 /**
@@ -37,9 +42,24 @@
  */
 public final class ConfigurationInternal {
 
+    @IntDef(prefix = "DETECTION_MODE_",
+            value = { DETECTION_MODE_UNKNOWN, DETECTION_MODE_MANUAL, DETECTION_MODE_GEO,
+                    DETECTION_MODE_TELEPHONY }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
+    @interface DetectionMode {};
+
+    public static final @DetectionMode int DETECTION_MODE_UNKNOWN = 0;
+    public static final @DetectionMode int DETECTION_MODE_MANUAL = 1;
+    public static final @DetectionMode int DETECTION_MODE_GEO = 2;
+    public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 3;
+
     private final boolean mTelephonyDetectionSupported;
     private final boolean mGeoDetectionSupported;
     private final boolean mTelephonyFallbackSupported;
+    private final boolean mGeoDetectionRunInBackgroundEnabled;
+    private final boolean mEnhancedMetricsCollectionEnabled;
     private final boolean mAutoDetectionEnabledSetting;
     private final @UserIdInt int mUserId;
     private final boolean mUserConfigAllowed;
@@ -50,6 +70,8 @@
         mTelephonyDetectionSupported = builder.mTelephonyDetectionSupported;
         mGeoDetectionSupported = builder.mGeoDetectionSupported;
         mTelephonyFallbackSupported = builder.mTelephonyFallbackSupported;
+        mGeoDetectionRunInBackgroundEnabled = builder.mGeoDetectionRunInBackgroundEnabled;
+        mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
         mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
         mUserId = builder.mUserId;
@@ -81,6 +103,25 @@
         return mTelephonyFallbackSupported;
     }
 
+    /**
+     * Returns {@code true} if location time zone detection should run all the time on supported
+     * devices, even when the user has not enabled it explicitly in settings. Enabled for internal
+     * testing only. See {@link #isGeoDetectionExecutionEnabled()} and {@link #getDetectionMode()}
+     * for details.
+     */
+    boolean getGeoDetectionRunInBackgroundEnabled() {
+        return mGeoDetectionRunInBackgroundEnabled;
+    }
+
+    /**
+     * Returns {@code true} if the device can collect / report extra metrics information for QA
+     * / testers. These metrics might involve logging more expensive or more revealing data that
+     * would not be collected from the set of public users.
+     */
+    public boolean isEnhancedMetricsCollectionEnabled() {
+        return mEnhancedMetricsCollectionEnabled;
+    }
+
     /** Returns the value of the auto time zone detection enabled setting. */
     public boolean getAutoDetectionEnabledSetting() {
         return mAutoDetectionEnabledSetting;
@@ -121,14 +162,31 @@
     }
 
     /**
-     * Returns true if geolocation time zone detection behavior is actually enabled, which can be
-     * distinct from the raw setting value.
+     * Returns the detection mode to use, i.e. which suggestions to use to determine the device's
+     * time zone.
      */
-    public boolean getGeoDetectionEnabledBehavior() {
-        return getAutoDetectionEnabledBehavior()
-                && isGeoDetectionSupported()
+    public @DetectionMode int getDetectionMode() {
+        if (!getAutoDetectionEnabledBehavior()) {
+            return DETECTION_MODE_MANUAL;
+        } else if (isGeoDetectionSupported() && getLocationEnabledSetting()
+                && getGeoDetectionEnabledSetting()) {
+            return DETECTION_MODE_GEO;
+        } else {
+            return DETECTION_MODE_TELEPHONY;
+        }
+    }
+
+    /**
+     * 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()}.
+     */
+    public boolean isGeoDetectionExecutionEnabled() {
+        return isGeoDetectionSupported()
                 && getLocationEnabledSetting()
-                && getGeoDetectionEnabledSetting();
+                && ((mAutoDetectionEnabledSetting && getGeoDetectionEnabledSetting())
+                || getGeoDetectionRunInBackgroundEnabled());
     }
 
     /** Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values. */
@@ -227,6 +285,8 @@
                 && mTelephonyDetectionSupported == that.mTelephonyDetectionSupported
                 && mGeoDetectionSupported == that.mGeoDetectionSupported
                 && mTelephonyFallbackSupported == that.mTelephonyFallbackSupported
+                && mGeoDetectionRunInBackgroundEnabled == that.mGeoDetectionRunInBackgroundEnabled
+                && mEnhancedMetricsCollectionEnabled == that.mEnhancedMetricsCollectionEnabled
                 && mAutoDetectionEnabledSetting == that.mAutoDetectionEnabledSetting
                 && mLocationEnabledSetting == that.mLocationEnabledSetting
                 && mGeoDetectionEnabledSetting == that.mGeoDetectionEnabledSetting;
@@ -235,8 +295,9 @@
     @Override
     public int hashCode() {
         return Objects.hash(mUserId, mUserConfigAllowed, mTelephonyDetectionSupported,
-                mGeoDetectionSupported, mTelephonyFallbackSupported, mAutoDetectionEnabledSetting,
-                mLocationEnabledSetting, mGeoDetectionEnabledSetting);
+                mGeoDetectionSupported, mTelephonyFallbackSupported,
+                mGeoDetectionRunInBackgroundEnabled, mEnhancedMetricsCollectionEnabled,
+                mAutoDetectionEnabledSetting, mLocationEnabledSetting, mGeoDetectionEnabledSetting);
     }
 
     @Override
@@ -247,6 +308,8 @@
                 + ", mTelephonyDetectionSupported=" + mTelephonyDetectionSupported
                 + ", mGeoDetectionSupported=" + mGeoDetectionSupported
                 + ", mTelephonyFallbackSupported=" + mTelephonyFallbackSupported
+                + ", mGeoDetectionRunInBackgroundEnabled=" + mGeoDetectionRunInBackgroundEnabled
+                + ", mEnhancedMetricsCollectionEnabled=" + mEnhancedMetricsCollectionEnabled
                 + ", mAutoDetectionEnabledSetting=" + mAutoDetectionEnabledSetting
                 + ", mLocationEnabledSetting=" + mLocationEnabledSetting
                 + ", mGeoDetectionEnabledSetting=" + mGeoDetectionEnabledSetting
@@ -264,6 +327,8 @@
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
         private boolean mTelephonyFallbackSupported;
+        private boolean mGeoDetectionRunInBackgroundEnabled;
+        private boolean mEnhancedMetricsCollectionEnabled;
         private boolean mAutoDetectionEnabledSetting;
         private boolean mLocationEnabledSetting;
         private boolean mGeoDetectionEnabledSetting;
@@ -284,6 +349,8 @@
             this.mTelephonyDetectionSupported = toCopy.mTelephonyDetectionSupported;
             this.mTelephonyFallbackSupported = toCopy.mTelephonyFallbackSupported;
             this.mGeoDetectionSupported = toCopy.mGeoDetectionSupported;
+            this.mGeoDetectionRunInBackgroundEnabled = toCopy.mGeoDetectionRunInBackgroundEnabled;
+            this.mEnhancedMetricsCollectionEnabled = toCopy.mEnhancedMetricsCollectionEnabled;
             this.mAutoDetectionEnabledSetting = toCopy.mAutoDetectionEnabledSetting;
             this.mLocationEnabledSetting = toCopy.mLocationEnabledSetting;
             this.mGeoDetectionEnabledSetting = toCopy.mGeoDetectionEnabledSetting;
@@ -323,6 +390,24 @@
         }
 
         /**
+         * Sets whether location time zone detection should run all the time on supported devices,
+         * even when the user has not enabled it explicitly in settings. Enabled for internal
+         * testing only.
+         */
+        public Builder setGeoDetectionRunInBackgroundEnabled(boolean enabled) {
+            mGeoDetectionRunInBackgroundEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets the value for enhanced metrics collection.
+         */
+        public Builder setEnhancedMetricsCollectionEnabled(boolean enabled) {
+            mEnhancedMetricsCollectionEnabled = enabled;
+            return this;
+        }
+
+        /**
          * Sets the value of the automatic time zone detection enabled setting for this device.
          */
         public Builder setAutoDetectionEnabledSetting(boolean enabled) {
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index f156f8c..6c36989 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -34,26 +34,32 @@
  * A class that provides time zone detector state information for metrics.
  *
  * <p>
- * Regarding time zone ID ordinals:
+ * Regarding the use of time zone ID ordinals in metrics / telemetry:
  * <p>
- * We don't want to leak user location information by reporting time zone IDs. Instead, time zone
- * IDs are consistently identified within a given instance of this class by a numeric ID. This
- * allows comparison of IDs without revealing what those IDs are.
+ * For general metrics, we don't want to leak user location information by reporting time zone
+ * IDs. Instead, time zone IDs are consistently identified within a given instance of this class by
+ * a numeric ID (ordinal). This allows comparison of IDs without revealing what those IDs are.
+ * See {@link #isEnhancedMetricsCollectionEnabled()} for the setting that enables actual IDs to be
+ * collected.
  */
 public final class MetricsTimeZoneDetectorState {
 
     @IntDef(prefix = "DETECTION_MODE_",
-            value = { DETECTION_MODE_MANUAL, DETECTION_MODE_GEO, DETECTION_MODE_TELEPHONY})
+            value = { DETECTION_MODE_UNKNOWN, DETECTION_MODE_MANUAL, DETECTION_MODE_GEO,
+                    DETECTION_MODE_TELEPHONY }
+    )
     @Retention(RetentionPolicy.SOURCE)
     @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
     public @interface DetectionMode {};
 
-    public static final @DetectionMode int DETECTION_MODE_MANUAL = 0;
-    public static final @DetectionMode int DETECTION_MODE_GEO = 1;
-    public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 2;
+    public static final @DetectionMode int DETECTION_MODE_UNKNOWN = 0;
+    public static final @DetectionMode int DETECTION_MODE_MANUAL = 1;
+    public static final @DetectionMode int DETECTION_MODE_GEO = 2;
+    public static final @DetectionMode int DETECTION_MODE_TELEPHONY = 3;
 
     @NonNull private final ConfigurationInternal mConfigurationInternal;
     private final int mDeviceTimeZoneIdOrdinal;
+    @Nullable private final String mDeviceTimeZoneId;
     @Nullable private final MetricsTimeZoneSuggestion mLatestManualSuggestion;
     @Nullable private final MetricsTimeZoneSuggestion mLatestTelephonySuggestion;
     @Nullable private final MetricsTimeZoneSuggestion mLatestGeolocationSuggestion;
@@ -61,11 +67,13 @@
     private MetricsTimeZoneDetectorState(
             @NonNull ConfigurationInternal configurationInternal,
             int deviceTimeZoneIdOrdinal,
+            @Nullable String deviceTimeZoneId,
             @Nullable MetricsTimeZoneSuggestion latestManualSuggestion,
             @Nullable MetricsTimeZoneSuggestion latestTelephonySuggestion,
             @Nullable MetricsTimeZoneSuggestion latestGeolocationSuggestion) {
         mConfigurationInternal = Objects.requireNonNull(configurationInternal);
         mDeviceTimeZoneIdOrdinal = deviceTimeZoneIdOrdinal;
+        mDeviceTimeZoneId = deviceTimeZoneId;
         mLatestManualSuggestion = latestManualSuggestion;
         mLatestTelephonySuggestion = latestTelephonySuggestion;
         mLatestGeolocationSuggestion = latestGeolocationSuggestion;
@@ -83,18 +91,24 @@
             @Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
             @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
 
+        boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
+        String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
         int deviceTimeZoneIdOrdinal =
                 tzIdOrdinalGenerator.ordinal(Objects.requireNonNull(deviceTimeZoneId));
         MetricsTimeZoneSuggestion latestCanonicalManualSuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestManualSuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestManualSuggestion, includeZoneIds);
         MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestTelephonySuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
         MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
-                createMetricsTimeZoneSuggestion(tzIdOrdinalGenerator, latestGeolocationSuggestion);
+                createMetricsTimeZoneSuggestion(
+                        tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
 
         return new MetricsTimeZoneDetectorState(
-                configurationInternal, deviceTimeZoneIdOrdinal, latestCanonicalManualSuggestion,
-                latestCanonicalTelephonySuggestion, latestCanonicalGeolocationSuggestion);
+                configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
+                latestCanonicalManualSuggestion, latestCanonicalTelephonySuggestion,
+                latestCanonicalGeolocationSuggestion);
     }
 
     /** Returns true if the device supports telephony time zone detection. */
@@ -112,6 +126,20 @@
         return mConfigurationInternal.isTelephonyFallbackSupported();
     }
 
+    /**
+     * Returns {@code true} if location time zone detection should run all the time on supported
+     * devices, even when the user has not enabled it explicitly in settings. Enabled for internal
+     * testing only.
+     */
+    public boolean getGeoDetectionRunInBackgroundEnabled() {
+        return mConfigurationInternal.getGeoDetectionRunInBackgroundEnabled();
+    }
+
+    /** Returns true if enhanced metric collection is enabled. */
+    public boolean isEnhancedMetricsCollectionEnabled() {
+        return mConfigurationInternal.isEnhancedMetricsCollectionEnabled();
+    }
+
     /** Returns true if user's location can be used generally. */
     public boolean getUserLocationEnabledSetting() {
         return mConfigurationInternal.getLocationEnabledSetting();
@@ -132,17 +160,20 @@
      * things besides the user's setting.
      */
     public @DetectionMode int getDetectionMode() {
-        if (!mConfigurationInternal.getAutoDetectionEnabledBehavior()) {
-            return DETECTION_MODE_MANUAL;
-        } else if (mConfigurationInternal.getGeoDetectionEnabledBehavior()) {
-            return DETECTION_MODE_GEO;
-        } else {
-            return DETECTION_MODE_TELEPHONY;
+        switch (mConfigurationInternal.getDetectionMode()) {
+            case ConfigurationInternal.DETECTION_MODE_MANUAL:
+                return DETECTION_MODE_MANUAL;
+            case ConfigurationInternal.DETECTION_MODE_GEO:
+                return DETECTION_MODE_GEO;
+            case ConfigurationInternal.DETECTION_MODE_TELEPHONY:
+                return DETECTION_MODE_TELEPHONY;
+            default:
+                return DETECTION_MODE_UNKNOWN;
         }
     }
 
     /**
-     * Returns the ordinal for the device's currently set time zone ID.
+     * Returns the ordinal for the device's current time zone ID.
      * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
      */
     public int getDeviceTimeZoneIdOrdinal() {
@@ -150,6 +181,16 @@
     }
 
     /**
+     * Returns the device's current time zone ID. This will only be populated if {@link
+     * #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+     * MetricsTimeZoneDetectorState} for details.
+     */
+    @Nullable
+    public String getDeviceTimeZoneId() {
+        return mDeviceTimeZoneId;
+    }
+
+    /**
      * Returns a canonical form of the last manual suggestion received.
      */
     @Nullable
@@ -183,6 +224,7 @@
         }
         MetricsTimeZoneDetectorState that = (MetricsTimeZoneDetectorState) o;
         return mDeviceTimeZoneIdOrdinal == that.mDeviceTimeZoneIdOrdinal
+                && Objects.equals(mDeviceTimeZoneId, that.mDeviceTimeZoneId)
                 && mConfigurationInternal.equals(that.mConfigurationInternal)
                 && Objects.equals(mLatestManualSuggestion, that.mLatestManualSuggestion)
                 && Objects.equals(mLatestTelephonySuggestion, that.mLatestTelephonySuggestion)
@@ -191,7 +233,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal,
+        return Objects.hash(mConfigurationInternal, mDeviceTimeZoneIdOrdinal, mDeviceTimeZoneId,
                 mLatestManualSuggestion, mLatestTelephonySuggestion, mLatestGeolocationSuggestion);
     }
 
@@ -200,6 +242,7 @@
         return "MetricsTimeZoneDetectorState{"
                 + "mConfigurationInternal=" + mConfigurationInternal
                 + ", mDeviceTimeZoneIdOrdinal=" + mDeviceTimeZoneIdOrdinal
+                + ", mDeviceTimeZoneId=" + mDeviceTimeZoneId
                 + ", mLatestManualSuggestion=" + mLatestManualSuggestion
                 + ", mLatestTelephonySuggestion=" + mLatestTelephonySuggestion
                 + ", mLatestGeolocationSuggestion=" + mLatestGeolocationSuggestion
@@ -209,34 +252,40 @@
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @NonNull ManualTimeZoneSuggestion manualSuggestion) {
+            @NonNull ManualTimeZoneSuggestion manualSuggestion,
+            boolean includeFullZoneIds) {
         if (manualSuggestion == null) {
             return null;
         }
 
-        int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(manualSuggestion.getZoneId());
-        return MetricsTimeZoneSuggestion.createCertain(
-                new int[] { zoneIdOrdinal });
+        String suggestionZoneId = manualSuggestion.getZoneId();
+        String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+        int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @NonNull TelephonyTimeZoneSuggestion telephonySuggestion) {
+            @NonNull TelephonyTimeZoneSuggestion telephonySuggestion,
+            boolean includeFullZoneIds) {
         if (telephonySuggestion == null) {
             return null;
         }
-        if (telephonySuggestion.getZoneId() == null) {
+        String suggestionZoneId = telephonySuggestion.getZoneId();
+        if (suggestionZoneId == null) {
             return MetricsTimeZoneSuggestion.createUncertain();
         }
-        int zoneIdOrdinal = zoneIdOrdinalGenerator.ordinal(telephonySuggestion.getZoneId());
-        return MetricsTimeZoneSuggestion.createCertain(new int[] { zoneIdOrdinal });
+        String[] metricZoneIds = includeFullZoneIds ? new String[] { suggestionZoneId } : null;
+        int[] zoneIdOrdinals = new int[] { zoneIdOrdinalGenerator.ordinal(suggestionZoneId) };
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     @Nullable
     private static MetricsTimeZoneSuggestion createMetricsTimeZoneSuggestion(
             @NonNull OrdinalGenerator<String> zoneIdOrdinalGenerator,
-            @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion) {
+            @Nullable GeolocationTimeZoneSuggestion geolocationSuggestion,
+            boolean includeFullZoneIds) {
         if (geolocationSuggestion == null) {
             return null;
         }
@@ -245,7 +294,9 @@
         if (zoneIds == null) {
             return MetricsTimeZoneSuggestion.createUncertain();
         }
-        return MetricsTimeZoneSuggestion.createCertain(zoneIdOrdinalGenerator.ordinals(zoneIds));
+        String[] metricZoneIds = includeFullZoneIds ? zoneIds.toArray(new String[0]) : null;
+        int[] zoneIdOrdinals = zoneIdOrdinalGenerator.ordinals(zoneIds);
+        return MetricsTimeZoneSuggestion.createCertain(metricZoneIds, zoneIdOrdinals);
     }
 
     /**
@@ -254,33 +305,49 @@
      * MetricsTimeZoneSuggestion proto definition.
      */
     public static final class MetricsTimeZoneSuggestion {
-        @Nullable
-        private final int[] mZoneIdOrdinals;
+        @Nullable private final String[] mZoneIds;
+        @Nullable private final int[] mZoneIdOrdinals;
 
-        MetricsTimeZoneSuggestion(@Nullable int[] zoneIdOrdinals) {
+        private MetricsTimeZoneSuggestion(
+                @Nullable String[] zoneIds, @Nullable int[] zoneIdOrdinals) {
+            mZoneIds = zoneIds;
             mZoneIdOrdinals = zoneIdOrdinals;
         }
 
         @NonNull
         static MetricsTimeZoneSuggestion createUncertain() {
-            return new MetricsTimeZoneSuggestion(null);
+            return new MetricsTimeZoneSuggestion(null, null);
         }
 
         @NonNull
         static MetricsTimeZoneSuggestion createCertain(
-                @NonNull int[] zoneIdOrdinals) {
-            return new MetricsTimeZoneSuggestion(zoneIdOrdinals);
+                @Nullable String[] zoneIds, @NonNull int[] zoneIdOrdinals) {
+            return new MetricsTimeZoneSuggestion(zoneIds, zoneIdOrdinals);
         }
 
         public boolean isCertain() {
             return mZoneIdOrdinals != null;
         }
 
+        /**
+         * Returns ordinals for the time zone IDs contained in the suggestion.
+         * See {@link MetricsTimeZoneDetectorState} for information about ordinals.
+         */
         @Nullable
         public int[] getZoneIdOrdinals() {
             return mZoneIdOrdinals;
         }
 
+        /**
+         * Returns the time zone IDs contained in the suggestion. This will only be populated if
+         * {@link #isEnhancedMetricsCollectionEnabled()} is {@code true}. See {@link
+         * MetricsTimeZoneDetectorState} for details.
+         */
+        @Nullable
+        public String[] getZoneIds() {
+            return mZoneIds;
+        }
+
         @Override
         public boolean equals(Object o) {
             if (this == o) {
@@ -290,18 +357,22 @@
                 return false;
             }
             MetricsTimeZoneSuggestion that = (MetricsTimeZoneSuggestion) o;
-            return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals);
+            return Arrays.equals(mZoneIdOrdinals, that.mZoneIdOrdinals)
+                    && Arrays.equals(mZoneIds, that.mZoneIds);
         }
 
         @Override
         public int hashCode() {
-            return Arrays.hashCode(mZoneIdOrdinals);
+            int result = Arrays.hashCode(mZoneIds);
+            result = 31 * result + Arrays.hashCode(mZoneIdOrdinals);
+            return result;
         }
 
         @Override
         public String toString() {
             return "MetricsTimeZoneSuggestion{"
                     + "mZoneIdOrdinals=" + Arrays.toString(mZoneIdOrdinals)
+                    + ", mZoneIds=" + Arrays.toString(mZoneIds)
                     + '}';
         }
     }
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index 02ea433..ae52912 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -36,7 +36,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.util.ArraySet;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -45,7 +44,6 @@
 
 import java.time.Duration;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -59,30 +57,31 @@
     /**
      * Device config keys that can affect the content of {@link ConfigurationInternal}.
      */
-    private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH =
-            Collections.unmodifiableSet(new ArraySet<>(new String[] {
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
-                    ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
-            }));
+    private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+            ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+            ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED
+    );
 
     /**
      * Device config keys that can affect {@link
      * com.android.server.timezonedetector.location.LocationTimeZoneManagerService} behavior.
      */
-    private static final Set<String> LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH =
-            Collections.unmodifiableSet(new ArraySet<>(new String[] {
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
-                    ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
-                    ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
-                    ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
-                    ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
-                    ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
-                    ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
-            }));
+    private static final Set<String> LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+            ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
+            ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
+            ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
+            ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+            ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+            ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
+    );
 
     private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
     private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
@@ -296,6 +295,8 @@
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
                 .setTelephonyFallbackSupported(isTelephonyFallbackSupported())
+                .setGeoDetectionRunInBackgroundEnabled(getGeoDetectionRunInBackgroundEnabled())
+                .setEnhancedMetricsCollectionEnabled(isEnhancedMetricsCollectionEnabled())
                 .setAutoDetectionEnabledSetting(getAutoDetectionEnabledSetting())
                 .setUserConfigAllowed(isUserConfigAllowed(userId))
                 .setLocationEnabledSetting(getLocationEnabledSetting(userId))
@@ -400,6 +401,29 @@
                 defaultEnabled);
     }
 
+    /**
+     * Returns {@code true} if location time zone detection should run all the time on supported
+     * devices, even when the user has not enabled it explicitly in settings. Enabled for internal
+     * testing only.
+     */
+    private boolean getGeoDetectionRunInBackgroundEnabled() {
+        final boolean defaultEnabled = false;
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+                defaultEnabled);
+    }
+
+    /**
+     * Returns {@code true} if extra metrics / telemetry information can be collected. Used for
+     * internal testers.
+     */
+    private boolean isEnhancedMetricsCollectionEnabled() {
+        final boolean defaultEnabled = false;
+        return mServerFlags.getBoolean(
+                ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+                defaultEnabled);
+    }
+
     @Override
     @NonNull
     public synchronized String getPrimaryLocationTimeZoneProviderPackageName() {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 14784cf..f75608e 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -364,6 +364,13 @@
         }
     }
 
+    @NonNull
+    MetricsTimeZoneDetectorState generateMetricsState() {
+        enforceManageTimeZoneDetectorPermission();
+
+        return mTimeZoneDetectorStrategy.generateMetricsState();
+    }
+
     @Override
     protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
             @Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 2b912ad..1d72ca5 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.timezonedetector;
 
+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_IS_AUTO_DETECTION_ENABLED;
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
@@ -28,7 +29,9 @@
 import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
 import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
 
+import static com.android.server.timedetector.ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED;
+import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT;
 import static com.android.server.timedetector.ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE;
 import static com.android.server.timedetector.ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED;
@@ -80,6 +83,8 @@
                 return runSuggestTelephonyTimeZone();
             case SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK:
                 return runEnableTelephonyFallback();
+            case SHELL_COMMAND_DUMP_METRICS:
+                return runDumpMetrics();
             default: {
                 return handleDefaultCommands(cmd);
             }
@@ -168,14 +173,22 @@
             pw.println("Suggestion " + suggestion + " injected.");
             return 0;
         } catch (RuntimeException e) {
-            pw.println(e.toString());
+            pw.println(e);
             return 1;
         }
     }
 
     private int runEnableTelephonyFallback() {
         mInterface.enableTelephonyFallback();
-        return 1;
+        return 0;
+    }
+
+    private int runDumpMetrics() {
+        final PrintWriter pw = getOutPrintWriter();
+        MetricsTimeZoneDetectorState metricsState = mInterface.generateMetricsState();
+        pw.println("MetricsTimeZoneDetectorState:");
+        pw.println(metricsState.toString());
+        return 0;
     }
 
     @Override
@@ -208,10 +221,10 @@
         pw.println();
         pw.printf("  %s <geolocation suggestion opts>\n",
                 SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
-        pw.printf("  %s <manual suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
-        pw.printf("  %s <telephony suggestion opts>\n",
-                SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+        pw.printf("  %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
+        pw.printf("  %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
+        pw.printf("  %s\n", SHELL_COMMAND_DUMP_METRICS);
+        pw.printf("    Dumps the service metrics to stdout for inspection.\n");
         pw.println();
         GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
         pw.println();
@@ -225,6 +238,9 @@
         pw.printf("    Only observed if the geolocation time zone detection feature is enabled in"
                 + " config.\n");
         pw.printf("    Set this to false to disable the feature.\n");
+        pw.printf("  %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED);
+        pw.printf("    Runs geolocation time zone detection even when it not enabled by the user."
+                + " The result is not used to set the device's time zone [*]\n");
         pw.printf("  %s\n", KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT);
         pw.printf("    Only used if the device does not have an explicit 'geolocation time zone"
                 + " detection enabled' setting stored [*].\n");
@@ -235,6 +251,8 @@
         pw.printf("  %s\n", KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED);
         pw.printf("    Used to enable / disable support for telephony detection fallback. Also see"
                 + " the %s command.\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
+        pw.printf("  %s\n", KEY_ENHANCED_METRICS_COLLECTION_ENABLED);
+        pw.printf("    Used to increase the detail of metrics collected / reported.\n");
         pw.println();
         pw.printf("[*] To be enabled, the user must still have location = on / auto time zone"
                 + " detection = on.\n");
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 6840511..e21d0e4 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -424,42 +424,47 @@
     @GuardedBy("this")
     private void doAutoTimeZoneDetection(
             @NonNull ConfigurationInternal currentUserConfig, @NonNull String detectionReason) {
-        if (!currentUserConfig.getAutoDetectionEnabledBehavior()) {
-            // Avoid doing unnecessary work.
-            return;
-        }
-
         // Use the correct algorithm based on the user's current configuration. If it changes, then
         // detection will be re-run.
-        if (currentUserConfig.getGeoDetectionEnabledBehavior()) {
-            boolean isGeoDetectionCertain = doGeolocationTimeZoneDetection(detectionReason);
+        switch (currentUserConfig.getDetectionMode()) {
+            case ConfigurationInternal.DETECTION_MODE_MANUAL:
+                // No work to do.
+                break;
+            case ConfigurationInternal.DETECTION_MODE_GEO: {
+                boolean isGeoDetectionCertain = doGeolocationTimeZoneDetection(detectionReason);
 
-            // When geolocation detection is uncertain of the time zone, telephony detection
-            // can be used if telephony fallback is enabled and supported.
-            if (!isGeoDetectionCertain
-                    && mTelephonyTimeZoneFallbackEnabled.getValue()
-                    && currentUserConfig.isTelephonyFallbackSupported()) {
+                // When geolocation detection is uncertain of the time zone, telephony detection
+                // can be used if telephony fallback is enabled and supported.
+                if (!isGeoDetectionCertain
+                        && mTelephonyTimeZoneFallbackEnabled.getValue()
+                        && currentUserConfig.isTelephonyFallbackSupported()) {
 
-                // This "only look at telephony if geolocation is uncertain" approach is
-                // deliberate to try to keep the logic simple and keep telephony and geolocation
-                // detection decoupled: when geolocation detection is in use, it is fully
-                // trusted and the most recent "certain" geolocation suggestion available will
-                // be used, even if the information it is based on is quite old.
-                // There could be newer telephony suggestions available, but telephony
-                // suggestions tend not to be withdrawn when they should be, and are based on
-                // combining information like MCC and NITZ signals, which could have been
-                // received at different times; thus it is hard to say what time the suggestion
-                // is actually "for" and reason clearly about ordering between telephony and
-                // geolocation suggestions.
-                //
-                // This approach is reliant on the location_time_zone_manager (and the location
-                // time zone providers it manages) correctly sending "uncertain" suggestions
-                // when the current location is unknown so that telephony fallback will actually be
-                // used.
-                doTelephonyTimeZoneDetection(detectionReason + ", telephony fallback mode");
+                    // This "only look at telephony if geolocation is uncertain" approach is
+                    // deliberate to try to keep the logic simple and keep telephony and geolocation
+                    // detection decoupled: when geolocation detection is in use, it is fully
+                    // trusted and the most recent "certain" geolocation suggestion available will
+                    // be used, even if the information it is based on is quite old.
+                    // There could be newer telephony suggestions available, but telephony
+                    // suggestions tend not to be withdrawn when they should be, and are based on
+                    // combining information like MCC and NITZ signals, which could have been
+                    // received at different times; thus it is hard to say what time the suggestion
+                    // is actually "for" and reason clearly about ordering between telephony and
+                    // geolocation suggestions.
+                    //
+                    // This approach is reliant on the location_time_zone_manager (and the location
+                    // time zone providers it manages) correctly sending "uncertain" suggestions
+                    // when the current location is unknown so that telephony fallback will actually
+                    // be used.
+                    doTelephonyTimeZoneDetection(detectionReason + ", telephony fallback mode");
+                }
+                break;
             }
-        } else  {
-            doTelephonyTimeZoneDetection(detectionReason);
+            case ConfigurationInternal.DETECTION_MODE_TELEPHONY:
+                doTelephonyTimeZoneDetection(detectionReason);
+                break;
+            default:
+                Slog.wtf(LOG_TAG, "Unknown detection mode: "
+                        + currentUserConfig.getDetectionMode());
         }
     }
 
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 b2d9a3f..a9b9884 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -363,10 +363,11 @@
 
         // Provider started / stopped states only need to be changed if geoDetectionEnabled has
         // changed.
-        boolean oldGeoDetectionEnabled = oldConfiguration != null
-                && oldConfiguration.getGeoDetectionEnabledBehavior();
-        boolean newGeoDetectionEnabled = newConfiguration.getGeoDetectionEnabledBehavior();
-        if (oldGeoDetectionEnabled == newGeoDetectionEnabled) {
+        boolean oldIsGeoDetectionExecutionEnabled = oldConfiguration != null
+                && oldConfiguration.isGeoDetectionExecutionEnabled();
+        boolean newIsGeoDetectionExecutionEnabled =
+                newConfiguration.isGeoDetectionExecutionEnabled();
+        if (oldIsGeoDetectionExecutionEnabled == newIsGeoDetectionExecutionEnabled) {
             return;
         }
 
@@ -379,7 +380,7 @@
         // 2) If (1), and the secondary instantly enters the {perm failed} state, the uncertainty
         //    timeout started when the primary entered {started uncertain} should be cancelled.
 
-        if (newGeoDetectionEnabled) {
+        if (newIsGeoDetectionExecutionEnabled) {
             setState(STATE_INITIALIZING);
 
             // Try to start the primary provider.
@@ -566,9 +567,9 @@
             return;
         }
 
-        if (!mCurrentUserConfiguration.getGeoDetectionEnabledBehavior()) {
-            // This should not happen: the provider should not be in an started state if the user
-            // does not have geodetection enabled.
+        if (!mCurrentUserConfiguration.isGeoDetectionExecutionEnabled()) {
+            // This should not happen: the provider should not be in a started state if
+            // geodetection is not enabled.
             warnLog("Provider=" + provider + " is started, but"
                     + " currentUserConfiguration=" + mCurrentUserConfiguration
                     + " suggests it shouldn't be.");
diff --git a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
index f12139d..d70f970 100644
--- a/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvIAppManagerService.java
@@ -42,6 +42,7 @@
 import android.media.tv.interactive.TvIAppService;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -239,6 +240,24 @@
         userState.mCallbacks.finishBroadcast();
     }
 
+    @GuardedBy("mLock")
+    private void notifyStateChangedLocked(
+            UserState userState, String iAppServiceId, int type, int state) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyRteStateChanged(iAppServiceId="
+                    + iAppServiceId + ", type=" + type + ", state=" + state + ")");
+        }
+        int n = userState.mCallbacks.beginBroadcast();
+        for (int i = 0; i < n; ++i) {
+            try {
+                userState.mCallbacks.getBroadcastItem(i).onStateChanged(iAppServiceId, type, state);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "failed to report RTE state changed", e);
+            }
+        }
+        userState.mCallbacks.finishBroadcast();
+    }
+
     private int getIAppUid(TvIAppInfo info) {
         try {
             return getContext().getPackageManager().getApplicationInfo(
@@ -546,6 +565,17 @@
     }
 
     @GuardedBy("mLock")
+    private ServiceState getServiceStateLocked(ComponentName component, int userId) {
+        UserState userState = getOrCreateUserStateLocked(userId);
+        ServiceState serviceState = userState.mServiceStateMap.get(component);
+        if (serviceState == null) {
+            throw new IllegalStateException("Service state not found for " + component + " (userId="
+                    + userId + ")");
+        }
+        return serviceState;
+    }
+
+    @GuardedBy("mLock")
     private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
         UserState userState = getOrCreateUserStateLocked(userId);
         return getSessionStateLocked(sessionToken, callingUid, userState);
@@ -604,6 +634,7 @@
 
         @Override
         public void prepare(String tiasId, int type, int userId) {
+            // TODO: bind service
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
                     Binder.getCallingUid(), userId, "prepare");
             final long identity = Binder.clearCallingIdentity();
@@ -617,8 +648,15 @@
                     }
                     ComponentName componentName = iAppState.mInfo.getComponent();
                     ServiceState serviceState = userState.mServiceStateMap.get(componentName);
-                    if (serviceState != null) {
+                    if (serviceState == null) {
+                        serviceState = new ServiceState(
+                                componentName, tiasId, resolvedUserId, true, type);
+                        userState.mServiceStateMap.put(componentName, serviceState);
+                    } else if (serviceState.mService != null) {
                         serviceState.mService.prepare(type);
+                    } else {
+                        serviceState.mPendingPrepare = true;
+                        serviceState.mPendingPrepareType = type;
                     }
                 }
             } catch (RemoteException e) {
@@ -629,6 +667,74 @@
         }
 
         @Override
+        public void notifyAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "notifyAppLinkInfo");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    if (iAppState == null) {
+                        Slogf.e(TAG, "failed to notifyAppLinkInfo - unknown TIAS id "
+                                + tiasId);
+                        return;
+                    }
+                    ComponentName componentName = iAppState.mInfo.getComponent();
+                    ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+                    if (serviceState == null) {
+                        serviceState = new ServiceState(
+                                componentName, tiasId, resolvedUserId);
+                        serviceState.addPendingAppLink(appLinkInfo);
+                        userState.mServiceStateMap.put(componentName, serviceState);
+                    } else if (serviceState.mService != null) {
+                        serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+                    } else {
+                        serviceState.addPendingAppLink(appLinkInfo);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "error in notifyAppLinkInfo", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void sendAppLinkCommand(String tiasId, Bundle command, int userId) {
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userId, "sendAppLinkCommand");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+                    TvIAppState iAppState = userState.mIAppMap.get(tiasId);
+                    if (iAppState == null) {
+                        Slogf.e(TAG, "failed to sendAppLinkCommand - unknown TIAS id "
+                                + tiasId);
+                        return;
+                    }
+                    ComponentName componentName = iAppState.mInfo.getComponent();
+                    ServiceState serviceState = userState.mServiceStateMap.get(componentName);
+                    if (serviceState == null) {
+                        serviceState = new ServiceState(
+                                componentName, tiasId, resolvedUserId);
+                        serviceState.addPendingAppLinkCommand(command);
+                        userState.mServiceStateMap.put(componentName, serviceState);
+                    } else if (serviceState.mService != null) {
+                        serviceState.mService.sendAppLinkCommand(command);
+                    } else {
+                        serviceState.addPendingAppLinkCommand(command);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slogf.e(TAG, "error in sendAppLinkCommand", e);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void createSession(final ITvIAppClient client, final String iAppServiceId, int type,
                 int seq, int userId) {
             final int callingUid = Binder.getCallingUid();
@@ -657,7 +763,8 @@
                     if (serviceState == null) {
                         int tiasUid = PackageManager.getApplicationInfoAsUserCached(
                                 iAppState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
-                        serviceState = new ServiceState(iAppState.mComponentName, resolvedUserId);
+                        serviceState = new ServiceState(
+                                iAppState.mComponentName, iAppServiceId, resolvedUserId);
                         userState.mServiceStateMap.put(iAppState.mComponentName, serviceState);
                     }
                     // Send a null token immediately while reconnecting.
@@ -1202,15 +1309,36 @@
         private final List<IBinder> mSessionTokens = new ArrayList<>();
         private final ServiceConnection mConnection;
         private final ComponentName mComponent;
+        private final String mIAppServiceId;
+        private final List<Bundle> mPendingAppLinkInfo = new ArrayList<>();
+        private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
 
+        private boolean mPendingPrepare = false;
+        private Integer mPendingPrepareType = null;
         private ITvIAppService mService;
         private ServiceCallback mCallback;
         private boolean mBound;
         private boolean mReconnecting;
 
-        private ServiceState(ComponentName component, int userId) {
+        private ServiceState(ComponentName component, String tias, int userId) {
+            this(component, tias, userId, false, null);
+        }
+
+        private ServiceState(ComponentName component, String tias, int userId,
+                boolean pendingPrepare, Integer prepareType) {
             mComponent = component;
+            mPendingPrepare = pendingPrepare;
+            mPendingPrepareType = prepareType;
             mConnection = new IAppServiceConnection(component, userId);
+            mIAppServiceId = tias;
+        }
+
+        private void addPendingAppLink(Bundle info) {
+            mPendingAppLinkInfo.add(info);
+        }
+
+        private void addPendingAppLinkCommand(Bundle command) {
+            mPendingAppLinkCommand.add(command);
         }
     }
 
@@ -1238,6 +1366,53 @@
                 ServiceState serviceState = userState.mServiceStateMap.get(mComponent);
                 serviceState.mService = ITvIAppService.Stub.asInterface(service);
 
+                if (serviceState.mPendingPrepare) {
+                    final long identity = Binder.clearCallingIdentity();
+                    try {
+                        serviceState.mService.prepare(serviceState.mPendingPrepareType);
+                        serviceState.mPendingPrepare = false;
+                        serviceState.mPendingPrepareType = null;
+                    } catch (RemoteException e) {
+                        Slogf.e(TAG, "error in prepare when onServiceConnected", e);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+
+                if (!serviceState.mPendingAppLinkInfo.isEmpty()) {
+                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkInfo.iterator();
+                            it.hasNext(); ) {
+                        Bundle appLinkInfo = it.next();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            serviceState.mService.notifyAppLinkInfo(appLinkInfo);
+                            it.remove();
+                        } catch (RemoteException e) {
+                            Slogf.e(TAG, "error in notifyAppLinkInfo(" + appLinkInfo
+                                    + ") when onServiceConnected", e);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
+                if (!serviceState.mPendingAppLinkCommand.isEmpty()) {
+                    for (Iterator<Bundle> it = serviceState.mPendingAppLinkCommand.iterator();
+                            it.hasNext(); ) {
+                        Bundle command = it.next();
+                        final long identity = Binder.clearCallingIdentity();
+                        try {
+                            serviceState.mService.sendAppLinkCommand(command);
+                            it.remove();
+                        } catch (RemoteException e) {
+                            Slogf.e(TAG, "error in sendAppLinkCommand(" + command
+                                    + ") when onServiceConnected", e);
+                        } finally {
+                            Binder.restoreCallingIdentity(identity);
+                        }
+                    }
+                }
+
                 List<IBinder> tokensToBeRemoved = new ArrayList<>();
 
                 // And create sessions, if any.
@@ -1286,6 +1461,21 @@
             mComponent = component;
             mUserId = userId;
         }
+
+        @Override
+        public void onStateChanged(int type, int state) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    ServiceState serviceState = getServiceStateLocked(mComponent, mUserId);
+                    String iAppServiceId = serviceState.mIAppServiceId;
+                    UserState userState = getUserStateLocked(mUserId);
+                    notifyStateChangedLocked(userState, iAppServiceId, type, state);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
     }
 
     private final class SessionCallback extends ITvIAppSessionCallback.Stub {
@@ -1358,6 +1548,42 @@
             }
         }
 
+        @Override
+        public void onCommandRequest(@TvIAppService.IAppServiceCommandType String cmdType,
+                Bundle parameters) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onCommandRequest (cmdType=" + cmdType + ", parameters="
+                            + parameters.toString() + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onCommandRequest(cmdType, parameters, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onCommandRequest", e);
+                }
+            }
+        }
+
+        @Override
+        public void onSessionStateChanged(int state) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onSessionStateChanged (state=" + state + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onSessionStateChanged(state, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onSessionStateChanged", e);
+                }
+            }
+        }
+
         @GuardedBy("mLock")
         private boolean addSessionTokenToClientStateLocked(ITvIAppSession session) {
             try {
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 0436460..e508260 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -37,6 +37,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
@@ -52,6 +53,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * This class provides a system service that manages the TV tuner resources.
@@ -64,6 +68,8 @@
 
     public static final int INVALID_CLIENT_ID = -1;
     private static final int MAX_CLIENT_PRIORITY = 1000;
+    private static final long INVALID_THREAD_ID = -1;
+    private static final long TRMS_LOCK_TIMEOUT = 500;
 
     // Map of the registered client profiles
     private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
@@ -94,6 +100,12 @@
     // Used to synchronize the access to the service.
     private final Object mLock = new Object();
 
+    private final ReentrantLock mLockForTRMSLock = new ReentrantLock();
+    private final Condition mTunerApiLockReleasedCV = mLockForTRMSLock.newCondition();
+    private int mTunerApiLockHolder = INVALID_CLIENT_ID;
+    private long mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
+    private int mTunerApiLockNestedCount = 0;
+
     public TunerResourceManagerService(@Nullable Context context) {
         super(context);
     }
@@ -231,21 +243,24 @@
 
         @Override
         public boolean requestFrontend(@NonNull TunerFrontendRequest request,
-                @NonNull int[] frontendHandle) throws RemoteException {
+                @NonNull int[] frontendHandle) {
             enforceTunerAccessPermission("requestFrontend");
             enforceTrmAccessPermission("requestFrontend");
             if (frontendHandle == null) {
-                throw new RemoteException("frontendHandle can't be null");
+                Slog.e(TAG, "frontendHandle can't be null");
+                return false;
             }
             synchronized (mLock) {
                 if (!checkClientExists(request.clientId)) {
-                    throw new RemoteException("Request frontend from unregistered client: "
+                    Slog.e(TAG, "Request frontend from unregistered client: "
                             + request.clientId);
+                    return false;
                 }
                 // If the request client is holding or sharing a frontend, throw an exception.
                 if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) {
-                    throw new RemoteException("Release frontend before requesting another one. "
-                            + "Client id: " + request.clientId);
+                    Slog.e(TAG, "Release frontend before requesting another one. Client id: "
+                            + request.clientId);
+                    return false;
                 }
                 return requestFrontendInternal(request, frontendHandle);
             }
@@ -511,6 +526,20 @@
         }
 
         @Override
+        public boolean acquireLock(int clientId, long clientThreadId) {
+            enforceTrmAccessPermission("acquireLock");
+            // this must not be locked with mLock
+            return acquireLockInternal(clientId, clientThreadId, TRMS_LOCK_TIMEOUT);
+        }
+
+        @Override
+        public boolean releaseLock(int clientId) {
+            enforceTrmAccessPermission("releaseLock");
+            // this must not be locked with mLock
+            return releaseLockInternal(clientId, TRMS_LOCK_TIMEOUT, false, false);
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
 
@@ -1127,7 +1156,8 @@
             ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId());
             if (ownerClient != null) {
                 for (int shareOwnerId : ownerClient.getShareFeClientIds()) {
-                    clearFrontendAndClientMapping(getClientProfile(shareOwnerId));
+                    reclaimResource(shareOwnerId,
+                            TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
                 }
             }
         }
@@ -1194,6 +1224,187 @@
         return true;
     }
 
+    // Return value is guaranteed to be positive
+    private long getElapsedTime(long begin) {
+        long now = SystemClock.uptimeMillis();
+        long elapsed;
+        if (now >= begin) {
+            elapsed = now - begin;
+        } else {
+            elapsed = Long.MAX_VALUE - begin + now;
+            if (elapsed < 0) {
+                elapsed = Long.MAX_VALUE;
+            }
+        }
+        return elapsed;
+    }
+
+    private boolean lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction) {
+        try {
+            if (mLockForTRMSLock.tryLock(timeoutMS, TimeUnit.MILLISECONDS)) {
+                return true;
+            } else {
+                Slog.e(TAG, "FAILED to lock mLockForTRMSLock in " + callerFunction
+                        + ", clientId:" + clientId + ", timeoutMS:" + timeoutMS
+                        + ", mTunerApiLockHolder:" + mTunerApiLockHolder);
+                return false;
+            }
+        } catch (InterruptedException ie) {
+            Slog.e(TAG, "exception thrown in " + callerFunction + ":" + ie);
+            if (mLockForTRMSLock.isHeldByCurrentThread()) {
+                mLockForTRMSLock.unlock();
+            }
+            return false;
+        }
+    }
+
+    private boolean acquireLockInternal(int clientId, long clientThreadId, long timeoutMS) {
+        long begin = SystemClock.uptimeMillis();
+
+        // Grab lock
+        if (!lockForTunerApiLock(clientId, timeoutMS, "acquireLockInternal()")) {
+            return false;
+        }
+
+        try {
+            boolean available = mTunerApiLockHolder == INVALID_CLIENT_ID;
+            boolean nestedSelf = (clientId == mTunerApiLockHolder)
+                    && (clientThreadId == mTunerApiLockHolderThreadId);
+            boolean recovery = false;
+
+            // Allow same thread to grab the lock multiple times
+            while (!available && !nestedSelf) {
+                // calculate how much time is left before timeout
+                long leftOverMS = timeoutMS - getElapsedTime(begin);
+                if (leftOverMS <= 0) {
+                    Slog.e(TAG, "FAILED:acquireLockInternal(" + clientId + ", " + clientThreadId
+                            + ", " + timeoutMS + ") - timed out, but will grant the lock to "
+                            + "the callee by stealing it from the current holder:"
+                            + mTunerApiLockHolder + "(" + mTunerApiLockHolderThreadId + "), "
+                            + "who likely failed to call releaseLock(), "
+                            + "to prevent this from becoming an unrecoverable error");
+                    // This should not normally happen, but there sometimes are cases where
+                    // in-flight tuner API execution gets scheduled even after binderDied(),
+                    // which can leave the in-flight execution dissappear/stopped in between
+                    // acquireLock and releaseLock
+                    recovery = true;
+                    break;
+                }
+
+                // Cond wait for left over time
+                mTunerApiLockReleasedCV.await(leftOverMS, TimeUnit.MILLISECONDS);
+
+                // Check the availability for "spurious wakeup"
+                // The case that was confirmed is that someone else can acquire this in between
+                // signal() and wakup from the above await()
+                available = mTunerApiLockHolder == INVALID_CLIENT_ID;
+
+                if (!available) {
+                    Slog.w(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + ", "
+                            + timeoutMS + ") - woken up from cond wait, but " + mTunerApiLockHolder
+                            + "(" + mTunerApiLockHolderThreadId + ") is already holding the lock. "
+                            + "Going to wait again if timeout hasn't reached yet");
+                }
+            }
+
+            // Will always grant unless exception is thrown (or lock is already held)
+            if (available || recovery) {
+                if (DEBUG) {
+                    Slog.d(TAG, "SUCCESS:acquireLockInternal(" + clientId + ", " + clientThreadId
+                            + ", " + timeoutMS + ")");
+                }
+
+                if (mTunerApiLockNestedCount != 0) {
+                    Slog.w(TAG, "Something is wrong as nestedCount(" + mTunerApiLockNestedCount
+                            + ") is not zero. Will overriding it to 1 anyways");
+                }
+
+                // set the caller to be the holder
+                mTunerApiLockHolder = clientId;
+                mTunerApiLockHolderThreadId = clientThreadId;
+                mTunerApiLockNestedCount = 1;
+            } else if (nestedSelf) {
+                // Increment the nested count so releaseLockInternal won't signal prematuredly
+                mTunerApiLockNestedCount++;
+                if (DEBUG) {
+                    Slog.d(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+                            + ", " + timeoutMS + ") - nested count incremented to "
+                            + mTunerApiLockNestedCount);
+                }
+            } else {
+                Slog.e(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+                        + ", " + timeoutMS + ") - should not reach here");
+            }
+            // return true in "recovery" so callee knows that the deadlock is possible
+            // only when the return value is false
+            return (available || nestedSelf || recovery);
+        } catch (InterruptedException ie) {
+            Slog.e(TAG, "exception thrown in acquireLockInternal(" + clientId + ", "
+                    + clientThreadId + ", " + timeoutMS + "):" + ie);
+            return false;
+        } finally {
+            if (mLockForTRMSLock.isHeldByCurrentThread()) {
+                mLockForTRMSLock.unlock();
+            }
+        }
+    }
+
+    private boolean releaseLockInternal(int clientId, long timeoutMS,
+            boolean ignoreNestedCount, boolean suppressError) {
+        // Grab lock first
+        if (!lockForTunerApiLock(clientId, timeoutMS, "releaseLockInternal()")) {
+            return false;
+        }
+
+        try {
+            if (mTunerApiLockHolder == clientId) {
+                // Should always reach here unless called from binderDied()
+                mTunerApiLockNestedCount--;
+                if (ignoreNestedCount || mTunerApiLockNestedCount <= 0) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "SUCCESS:releaseLockInternal(" + clientId + ", " + timeoutMS
+                                + ", " + ignoreNestedCount + ", " + suppressError
+                                + ") - signaling!");
+                    }
+                    // Reset the current holder and signal
+                    mTunerApiLockHolder = INVALID_CLIENT_ID;
+                    mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
+                    mTunerApiLockNestedCount = 0;
+                    mTunerApiLockReleasedCV.signal();
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+                                + ", " + ignoreNestedCount + ", " + suppressError
+                                + ") - NOT signaling because nested count is not zero ("
+                                + mTunerApiLockNestedCount + ")");
+                    }
+                }
+                return true;
+            } else if (mTunerApiLockHolder == INVALID_CLIENT_ID) {
+                if (!suppressError) {
+                    Slog.w(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+                            + ") - called while there is no current holder");
+                }
+                // No need to do anything.
+                // Shouldn't reach here unless called from binderDied()
+                return false;
+            } else {
+                if (!suppressError) {
+                    Slog.e(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+                            + ") - called while someone else:" + mTunerApiLockHolder
+                            + "is the current holder");
+                }
+                // Cannot reset the holder Id because it reaches here when called
+                // from binderDied()
+                return false;
+            }
+        } finally {
+            if (mLockForTRMSLock.isHeldByCurrentThread()) {
+                mLockForTRMSLock.unlock();
+            }
+        }
+    }
+
     @VisibleForTesting
     protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
         private final IResourcesReclaimListener mListener;
@@ -1206,10 +1417,15 @@
 
         @Override
         public void binderDied() {
-            synchronized (mLock) {
-                if (checkClientExists(mClientId)) {
-                    removeClientProfile(mClientId);
+            try {
+                synchronized (mLock) {
+                    if (checkClientExists(mClientId)) {
+                        removeClientProfile(mClientId);
+                    }
                 }
+            } finally {
+                // reset the tuner API lock
+                releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true);
             }
         }
 
@@ -1247,6 +1463,13 @@
     protected boolean reclaimResource(int reclaimingClientId,
             @TunerResourceManager.TunerResourceType int resourceType) {
 
+        // Allowing this because:
+        // 1) serialization of resource reclaim is required in the current design
+        // 2) the outgoing transaction is handled by the system app (with
+        //    android.Manifest.permission.TUNER_RESOURCE_ACCESS), which goes through full
+        //    Google certification
+        Binder.allowBlockingForCurrentThread();
+
         // Reclaim all the resources of the share owners of the frontend that is used by the current
         // resource reclaimed client.
         ClientProfile profile = getClientProfile(reclaimingClientId);
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index b17257a..3442704 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -117,7 +117,6 @@
     PackageManagerInternal mPmInternal;
 
     /** File storing persisted {@link #mGrantedUriPermissions}. */
-    @GuardedBy("mLock")
     private final AtomicFile mGrantFile;
 
     /** XML constants used in {@link #mGrantFile} */
@@ -1299,15 +1298,14 @@
         return false;
     }
 
-    @GuardedBy("mLock")
-    private void writeGrantedUriPermissionsLocked() {
+    private void writeGrantedUriPermissions() {
         if (DEBUG) Slog.v(TAG, "writeGrantedUriPermissions()");
 
         final long startTime = SystemClock.uptimeMillis();
 
         // Snapshot permissions so we can persist without lock
         ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
-        synchronized (this) {
+        synchronized (mLock) {
             final int size = mGrantedUriPermissions.size();
             for (int i = 0; i < size; i++) {
                 final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
@@ -1330,8 +1328,8 @@
                 out.startTag(null, TAG_URI_GRANT);
                 out.attributeInt(null, ATTR_SOURCE_USER_ID, perm.uri.sourceUserId);
                 out.attributeInt(null, ATTR_TARGET_USER_ID, perm.targetUserId);
-                out.attribute(null, ATTR_SOURCE_PKG, perm.sourcePkg);
-                out.attribute(null, ATTR_TARGET_PKG, perm.targetPkg);
+                out.attributeInterned(null, ATTR_SOURCE_PKG, perm.sourcePkg);
+                out.attributeInterned(null, ATTR_TARGET_PKG, perm.targetPkg);
                 out.attribute(null, ATTR_URI, String.valueOf(perm.uri.uri));
                 writeBooleanAttribute(out, ATTR_PREFIX, perm.uri.prefix);
                 out.attributeInt(null, ATTR_MODE_FLAGS, perm.persistedModeFlags);
@@ -1360,9 +1358,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case PERSIST_URI_GRANTS_MSG: {
-                    synchronized (mLock) {
-                        writeGrantedUriPermissionsLocked();
-                    }
+                    writeGrantedUriPermissions();
                     break;
                 }
             }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index be13168..8b80b4a 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -77,6 +77,7 @@
 import android.os.PowerManager.WakeLock;
 import android.os.Process;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -686,6 +687,7 @@
         mUnderlyingNetworkController =
                 mDeps.newUnderlyingNetworkController(
                         mVcnContext,
+                        mConnectionConfig,
                         subscriptionGroup,
                         mLastSnapshot,
                         mUnderlyingNetworkControllerCallback);
@@ -788,8 +790,19 @@
             // TODO(b/179091925): Move the delayed-message handling to BaseState
 
             // If underlying is null, all underlying networks have been lost. Disconnect VCN after a
-            // timeout.
+            // timeout (or immediately if in airplane mode, since the device user has indicated that
+            // the radios should all be turned off).
             if (underlying == null) {
+                if (mDeps.isAirplaneModeOn(mVcnContext)) {
+                    sendMessageAndAcquireWakeLock(
+                            EVENT_UNDERLYING_NETWORK_CHANGED,
+                            TOKEN_ALL,
+                            new EventUnderlyingNetworkChangedInfo(null));
+                    sendDisconnectRequestedAndAcquireWakelock(
+                            DISCONNECT_REASON_UNDERLYING_NETWORK_LOST, false /* shouldQuit */);
+                    return;
+                }
+
                 setDisconnectRequestAlarm();
             } else {
                 // Received a new Network so any previous alarm is irrelevant - cancel + clear it,
@@ -2364,11 +2377,12 @@
         /** Builds a new UnderlyingNetworkController. */
         public UnderlyingNetworkController newUnderlyingNetworkController(
                 VcnContext vcnContext,
+                VcnGatewayConnectionConfig connectionConfig,
                 ParcelUuid subscriptionGroup,
                 TelephonySubscriptionSnapshot snapshot,
                 UnderlyingNetworkControllerCallback callback) {
             return new UnderlyingNetworkController(
-                    vcnContext, subscriptionGroup, snapshot, callback);
+                    vcnContext, connectionConfig, subscriptionGroup, snapshot, callback);
         }
 
         /** Builds a new IkeSession. */
@@ -2424,6 +2438,12 @@
                     validationStatusCallback);
         }
 
+        /** Checks if airplane mode is enabled. */
+        public boolean isAirplaneModeOn(@NonNull VcnContext vcnContext) {
+            return Settings.Global.getInt(vcnContext.getContext().getContentResolver(),
+                    Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+        }
+
         /** Gets the elapsed real time since boot, in millis. */
         public long getElapsedRealTime() {
             return SystemClock.elapsedRealtime();
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index bea8ae9..4576957 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -15,25 +15,36 @@
  */
 package com.android.server.vcn.routeselection;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
 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.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
 
 import static com.android.server.VcnManagementService.LOCAL_LOG;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkPriority;
 import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
+import android.net.vcn.VcnWifiUnderlyingNetworkPriority;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 import android.util.Slog;
-import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
 
+import java.util.LinkedHashSet;
 import java.util.Set;
 
 /** @hide */
@@ -56,52 +67,20 @@
      */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
-    /** Priority for any cellular network for which the subscription is listed as opportunistic */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0;
-    /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_WIFI_IN_USE = 1;
-    /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_WIFI_PROSPECTIVE = 2;
-    /** Priority for any standard macro cellular network */
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    static final int PRIORITY_MACRO_CELLULAR = 3;
+
     /** Priority for any other networks (including unvalidated, etc) */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int PRIORITY_ANY = Integer.MAX_VALUE;
 
-    private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>();
-
-    static {
-        PRIORITY_TO_STRING_MAP.put(
-                PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR");
-        PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY");
-    }
-
-    /**
-     * Gives networks a priority class, based on the following priorities:
-     *
-     * <ol>
-     *   <li>Opportunistic cellular
-     *   <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
-     *   <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
-     *   <li>Macro cellular
-     *   <li>Any others
-     * </ol>
-     */
-    static int calculatePriorityClass(
+    /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
+    public static int calculatePriorityClass(
+            VcnContext vcnContext,
             UnderlyingNetworkRecord networkRecord,
+            LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundle carrierConfig) {
-        final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
         // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
 
         if (networkRecord.isBlocked) {
@@ -109,8 +88,167 @@
             return PRIORITY_ANY;
         }
 
-        if (caps.hasTransport(TRANSPORT_CELLULAR)
-                && isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+        if (snapshot == null) {
+            logWtf("Got null snapshot");
+            return PRIORITY_ANY;
+        }
+
+        int priorityIndex = 0;
+        for (VcnUnderlyingNetworkPriority nwPriority : underlyingNetworkPriorities) {
+            if (checkMatchesPriorityRule(
+                    vcnContext,
+                    nwPriority,
+                    networkRecord,
+                    subscriptionGroup,
+                    snapshot,
+                    currentlySelected,
+                    carrierConfig)) {
+                return priorityIndex;
+            }
+            priorityIndex++;
+        }
+        return PRIORITY_ANY;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesPriorityRule(
+            VcnContext vcnContext,
+            VcnUnderlyingNetworkPriority networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        // TODO: Check Network Quality reported by metric monitors/probers.
+
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+            return false;
+        }
+
+        if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
+            return true;
+        }
+
+        if (networkPriority instanceof VcnWifiUnderlyingNetworkPriority) {
+            return checkMatchesWifiPriorityRule(
+                    (VcnWifiUnderlyingNetworkPriority) networkPriority,
+                    networkRecord,
+                    currentlySelected,
+                    carrierConfig);
+        }
+
+        if (networkPriority instanceof VcnCellUnderlyingNetworkPriority) {
+            return checkMatchesCellPriorityRule(
+                    vcnContext,
+                    (VcnCellUnderlyingNetworkPriority) networkPriority,
+                    networkRecord,
+                    subscriptionGroup,
+                    snapshot);
+        }
+
+        logWtf(
+                "Got unknown VcnUnderlyingNetworkPriority class: "
+                        + networkPriority.getClass().getSimpleName());
+        return false;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesWifiPriorityRule(
+            VcnWifiUnderlyingNetworkPriority networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (!caps.hasTransport(TRANSPORT_WIFI)) {
+            return false;
+        }
+
+        // TODO: Move the Network Quality check to the network metric monitor framework.
+        if (networkPriority.getNetworkQuality()
+                > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) {
+            return false;
+        }
+
+        if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private static int getWifiQuality(
+            UnderlyingNetworkRecord networkRecord,
+            UnderlyingNetworkRecord currentlySelected,
+            PersistableBundle carrierConfig) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+        final boolean isSelectedNetwork =
+                currentlySelected != null
+                        && networkRecord.network.equals(currentlySelected.network);
+
+        if (isSelectedNetwork
+                && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+            return NETWORK_QUALITY_OK;
+        }
+
+        if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
+            return NETWORK_QUALITY_OK;
+        }
+
+        return NETWORK_QUALITY_ANY;
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static boolean checkMatchesCellPriorityRule(
+            VcnContext vcnContext,
+            VcnCellUnderlyingNetworkPriority networkPriority,
+            UnderlyingNetworkRecord networkRecord,
+            ParcelUuid subscriptionGroup,
+            TelephonySubscriptionSnapshot snapshot) {
+        final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+        if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
+            return false;
+        }
+
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
+        if (telephonyNetworkSpecifier == null) {
+            logWtf("Got null NetworkSpecifier");
+            return false;
+        }
+
+        final int subId = telephonyNetworkSpecifier.getSubscriptionId();
+        final TelephonyManager subIdSpecificTelephonyMgr =
+                vcnContext
+                        .getContext()
+                        .getSystemService(TelephonyManager.class)
+                        .createForSubscriptionId(subId);
+
+        if (!networkPriority.getAllowedOperatorPlmnIds().isEmpty()) {
+            final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
+            if (!networkPriority.getAllowedOperatorPlmnIds().contains(plmnId)) {
+                return false;
+            }
+        }
+
+        if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) {
+            final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
+            if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) {
+                return false;
+            }
+        }
+
+        if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+            return false;
+        }
+
+        if (networkPriority.requireOpportunistic()) {
+            if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+                return false;
+            }
+
             // If this carrier is the active data provider, ensure that opportunistic is only
             // ever prioritized if it is also the active data subscription. This ensures that
             // if an opportunistic subscription is still in the process of being switched to,
@@ -121,36 +259,15 @@
             // Allow the following two cases:
             // 1. Active subId is NOT in the group that this VCN is supporting
             // 2. This opportunistic subscription is for the active subId
-            if (!snapshot.getAllSubIdsInGroup(subscriptionGroup)
+            if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())
-                    || caps.getSubscriptionIds()
+                    && !caps.getSubscriptionIds()
                             .contains(SubscriptionManager.getActiveDataSubscriptionId())) {
-                return PRIORITY_OPPORTUNISTIC_CELLULAR;
+                return false;
             }
         }
 
-        if (caps.hasTransport(TRANSPORT_WIFI)) {
-            if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)
-                    && currentlySelected != null
-                    && networkRecord.network.equals(currentlySelected.network)) {
-                return PRIORITY_WIFI_IN_USE;
-            }
-
-            if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
-                return PRIORITY_WIFI_PROSPECTIVE;
-            }
-        }
-
-        // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might
-        // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be
-        // the case if the Default Data SubId does not support certain services (eg voice
-        // calling)
-        if (caps.hasTransport(TRANSPORT_CELLULAR)
-                && !isOpportunistic(snapshot, caps.getSubscriptionIds())) {
-            return PRIORITY_MACRO_CELLULAR;
-        }
-
-        return PRIORITY_ANY;
+        return true;
     }
 
     static boolean isOpportunistic(
@@ -185,10 +302,6 @@
         return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
     }
 
-    static String priorityClassToString(int priorityClass) {
-        return PRIORITY_TO_STRING_MAP.get(priorityClass, "unknown");
-    }
-
     private static void logWtf(String msg) {
         Slog.wtf(TAG, msg);
         LOCAL_LOG.log(TAG + " WTF: " + msg);
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 071c7a6..cd124ee 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -32,6 +32,8 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
@@ -68,6 +70,7 @@
     @NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
 
     @NonNull private final VcnContext mVcnContext;
+    @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
     @NonNull private final ParcelUuid mSubscriptionGroup;
     @NonNull private final UnderlyingNetworkControllerCallback mCb;
     @NonNull private final Dependencies mDeps;
@@ -91,24 +94,22 @@
 
     public UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
             @NonNull UnderlyingNetworkControllerCallback cb) {
-        this(
-                vcnContext,
-                subscriptionGroup,
-                snapshot,
-                cb,
-                new Dependencies());
+        this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
     }
 
     private UnderlyingNetworkController(
             @NonNull VcnContext vcnContext,
+            @NonNull VcnGatewayConnectionConfig connectionConfig,
             @NonNull ParcelUuid subscriptionGroup,
             @NonNull TelephonySubscriptionSnapshot snapshot,
             @NonNull UnderlyingNetworkControllerCallback cb,
             @NonNull Dependencies deps) {
         mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+        mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
         mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
         mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
         mCb = Objects.requireNonNull(cb, "Missing cb");
@@ -399,6 +400,8 @@
             TreeSet<UnderlyingNetworkRecord> sorted =
                     new TreeSet<>(
                             UnderlyingNetworkRecord.getComparator(
+                                    mVcnContext,
+                                    mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
                                     mSubscriptionGroup,
                                     mLastSnapshot,
                                     mCurrentRecord,
@@ -495,12 +498,31 @@
         pw.println(
                 "Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
 
+        pw.println("VcnUnderlyingNetworkPriority list:");
+        pw.increaseIndent();
+        int index = 0;
+        for (VcnUnderlyingNetworkPriority priority :
+                mConnectionConfig.getVcnUnderlyingNetworkPriorities()) {
+            pw.println("Priority index: " + index);
+            priority.dump(pw);
+            index++;
+        }
+        pw.decreaseIndent();
+        pw.println();
+
         pw.println("Underlying networks:");
         pw.increaseIndent();
         if (mRouteSelectionCallback != null) {
             for (UnderlyingNetworkRecord record :
                     mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
-                record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig);
+                record.dump(
+                        mVcnContext,
+                        pw,
+                        mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+                        mSubscriptionGroup,
+                        mLastSnapshot,
+                        mCurrentRecord,
+                        mCarrierConfig);
             }
         }
         pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index 65c69de..27ba854 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -21,6 +21,7 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
 import android.os.ParcelUuid;
 import android.os.PersistableBundle;
 
@@ -28,8 +29,10 @@
 import com.android.internal.annotations.VisibleForTesting.Visibility;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
 
 import java.util.Comparator;
+import java.util.LinkedHashSet;
 import java.util.Objects;
 
 /**
@@ -73,22 +76,64 @@
     }
 
     static Comparator<UnderlyingNetworkRecord> getComparator(
+            VcnContext vcnContext,
+            LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
             PersistableBundle carrierConfig) {
         return (left, right) -> {
-            return Integer.compare(
+            final int leftIndex =
                     NetworkPriorityClassifier.calculatePriorityClass(
-                            left, subscriptionGroup, snapshot, currentlySelected, carrierConfig),
+                            vcnContext,
+                            left,
+                            underlyingNetworkPriorities,
+                            subscriptionGroup,
+                            snapshot,
+                            currentlySelected,
+                            carrierConfig);
+            final int rightIndex =
                     NetworkPriorityClassifier.calculatePriorityClass(
-                            right, subscriptionGroup, snapshot, currentlySelected, carrierConfig));
+                            vcnContext,
+                            right,
+                            underlyingNetworkPriorities,
+                            subscriptionGroup,
+                            snapshot,
+                            currentlySelected,
+                            carrierConfig);
+
+            // In the case of networks in the same priority class, prioritize based on other
+            // criteria (eg. actively selected network, link metrics, etc)
+            if (leftIndex == rightIndex) {
+                // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
+                // fall into the same priority class.
+                if (isSelected(left, currentlySelected)) {
+                    return -1;
+                }
+                if (isSelected(left, currentlySelected)) {
+                    return 1;
+                }
+            }
+            return Integer.compare(leftIndex, rightIndex);
         };
     }
 
+    private static boolean isSelected(
+            UnderlyingNetworkRecord recordToCheck, UnderlyingNetworkRecord currentlySelected) {
+        if (currentlySelected == null) {
+            return false;
+        }
+        if (currentlySelected.network == recordToCheck.network) {
+            return true;
+        }
+        return false;
+    }
+
     /** Dumps the state of this record for logging and debugging purposes. */
     void dump(
+            VcnContext vcnContext,
             IndentingPrintWriter pw,
+            LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
             ParcelUuid subscriptionGroup,
             TelephonySubscriptionSnapshot snapshot,
             UnderlyingNetworkRecord currentlySelected,
@@ -96,15 +141,17 @@
         pw.println("UnderlyingNetworkRecord:");
         pw.increaseIndent();
 
-        final int priorityClass =
+        final int priorityIndex =
                 NetworkPriorityClassifier.calculatePriorityClass(
-                        this, subscriptionGroup, snapshot, currentlySelected, carrierConfig);
-        pw.println(
-                "Priority class: "
-                        + NetworkPriorityClassifier.priorityClassToString(priorityClass)
-                        + " ("
-                        + priorityClass
-                        + ")");
+                        vcnContext,
+                        this,
+                        underlyingNetworkPriorities,
+                        subscriptionGroup,
+                        snapshot,
+                        currentlySelected,
+                        carrierConfig);
+
+        pw.println("Priority index:" + priorityIndex);
         pw.println("mNetwork: " + network);
         pw.println("mNetworkCapabilities: " + networkCapabilities);
         pw.println("mLinkProperties: " + linkProperties);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 658984a..2ec42b4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2215,6 +2215,19 @@
                 throw new IllegalArgumentException("padding must be positive: " + padding);
             }
 
+            int maxSize = getMaximumSizeDimension(displayId);
+
+            final int paddingWidth = padding.left + padding.right;
+            final int paddingHeight = padding.top + padding.bottom;
+            if (paddingWidth > maxSize) {
+                throw new IllegalArgumentException("padding width " + paddingWidth
+                        + " exceeds max width " + maxSize);
+            }
+            if (paddingHeight > maxSize) {
+                throw new IllegalArgumentException("padding height " + paddingHeight
+                        + " exceeds max height " + maxSize);
+            }
+
             final DisplayData wpdData = getDisplayDataOrCreate(displayId);
             if (!padding.equals(wpdData.mPadding)) {
                 wpdData.mPadding.set(padding);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index ec0b5f0..0396a11 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -98,6 +98,7 @@
 import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.TraceBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
@@ -277,20 +278,6 @@
         }
     }
 
-    void onRectangleOnScreenRequested(int displayId, Rect rectangle) {
-        if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-            mAccessibilityTracing.logTrace(
-                    TAG + ".onRectangleOnScreenRequested",
-                    FLAGS_MAGNIFICATION_CALLBACK,
-                    "displayId=" + displayId + "; rectangle={" + rectangle + "}");
-        }
-        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
-        if (displayMagnifier != null) {
-            displayMagnifier.onRectangleOnScreenRequested(rectangle);
-        }
-        // Not relevant for the window observer.
-    }
-
     void onWindowLayersChanged(int displayId) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
                 | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -634,31 +621,6 @@
             return mForceShowMagnifiableBounds;
         }
 
-        void onRectangleOnScreenRequested(Rect rectangle) {
-            if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
-                mAccessibilityTracing.logTrace(LOG_TAG + ".onRectangleOnScreenRequested",
-                        FLAGS_MAGNIFICATION_CALLBACK, "rectangle={" + rectangle + "}");
-            }
-            if (DEBUG_RECTANGLE_REQUESTED) {
-                Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
-            }
-            if (!mMagnifedViewport.isMagnifying()) {
-                return;
-            }
-            Rect magnifiedRegionBounds = mTempRect2;
-            mMagnifedViewport.getMagnifiedFrameInContentCoords(magnifiedRegionBounds);
-            if (magnifiedRegionBounds.contains(rectangle)) {
-                return;
-            }
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = rectangle.left;
-            args.argi2 = rectangle.top;
-            args.argi3 = rectangle.right;
-            args.argi4 = rectangle.bottom;
-            mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
-                    args).sendToTarget();
-        }
-
         void onWindowLayersChanged() {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(
@@ -1352,7 +1314,6 @@
 
         private class MyHandler extends Handler {
             public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
-            public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
             public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
             public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
             public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
@@ -1372,16 +1333,6 @@
                         magnifiedBounds.recycle();
                     } break;
 
-                    case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
-                        SomeArgs args = (SomeArgs) message.obj;
-                        final int left = args.argi1;
-                        final int top = args.argi2;
-                        final int right = args.argi3;
-                        final int bottom = args.argi4;
-                        mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
-                        args.recycle();
-                    } break;
-
                     case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
                         mCallbacks.onUserContextChanged();
                     } break;
@@ -1902,7 +1853,7 @@
         }
     }
 
-    private static final class AccessibilityControllerInternalImpl
+    static final class AccessibilityControllerInternalImpl
             implements AccessibilityControllerInternal {
 
         private static AccessibilityControllerInternalImpl sInstance;
@@ -1917,8 +1868,11 @@
 
         private final AccessibilityTracing mTracing;
         private volatile long mEnabledTracingFlags;
+        private UiChangesForAccessibilityCallbacksDispatcher mCallbacksDispatcher;
+        private final Looper mLooper;
 
         private AccessibilityControllerInternalImpl(WindowManagerService service) {
+            mLooper = service.mH.getLooper();
             mTracing = AccessibilityTracing.getInstance(service);
             mEnabledTracingFlags = 0L;
         }
@@ -1972,6 +1926,90 @@
             mTracing.logState(where, loggingTypes, callingParams, a11yDump, callingUid, callStack,
                     timeStamp, processId, threadId, ignoreStackEntries);
         }
+
+        @Override
+        public void setUiChangesForAccessibilityCallbacks(
+                UiChangesForAccessibilityCallbacks callbacks) {
+            if (isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                logTrace(
+                        TAG + ".setAccessibilityWindowManagerCallbacks",
+                        FLAGS_MAGNIFICATION_CALLBACK,
+                        "callbacks={" + callbacks + "}");
+            }
+            if (callbacks != null) {
+                if (mCallbacksDispatcher != null) {
+                    throw new IllegalStateException("Accessibility window manager callback already "
+                            + "set!");
+                }
+                mCallbacksDispatcher =
+                        new UiChangesForAccessibilityCallbacksDispatcher(this, mLooper,
+                                callbacks);
+            } else {
+                if (mCallbacksDispatcher == null) {
+                    throw new IllegalStateException("Accessibility window manager callback already "
+                            + "cleared!");
+                }
+                mCallbacksDispatcher = null;
+            }
+        }
+
+        public boolean hasWindowManagerEventDispatcher() {
+            if (isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
+                    | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
+                logTrace(TAG + ".hasCallbacks",
+                        FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK);
+            }
+            return mCallbacksDispatcher != null;
+        }
+
+        public void onRectangleOnScreenRequested(int displayId, Rect rectangle) {
+            if (isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                logTrace(
+                        TAG + ".onRectangleOnScreenRequested",
+                        FLAGS_MAGNIFICATION_CALLBACK,
+                        "rectangle={" + rectangle + "}");
+            }
+            if (mCallbacksDispatcher != null) {
+                mCallbacksDispatcher.onRectangleOnScreenRequested(displayId, rectangle);
+            }
+        }
+
+        private static final class UiChangesForAccessibilityCallbacksDispatcher {
+
+            private static final String LOG_TAG = TAG_WITH_CLASS_NAME
+                    ? "WindowManagerEventDispatcher" : TAG_WM;
+
+            private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
+
+            private final AccessibilityControllerInternalImpl mAccessibilityTracing;
+
+            @NonNull
+            private final UiChangesForAccessibilityCallbacks mCallbacks;
+
+            private final Handler mHandler;
+
+            UiChangesForAccessibilityCallbacksDispatcher(
+                    AccessibilityControllerInternalImpl accessibilityControllerInternal,
+                    Looper looper, @NonNull UiChangesForAccessibilityCallbacks callbacks) {
+                mAccessibilityTracing = accessibilityControllerInternal;
+                mCallbacks = callbacks;
+                mHandler = new Handler(looper);
+            }
+
+            void onRectangleOnScreenRequested(int displayId, Rect rectangle) {
+                if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
+                    mAccessibilityTracing.logTrace(LOG_TAG + ".onRectangleOnScreenRequested",
+                            FLAGS_MAGNIFICATION_CALLBACK, "rectangle={" + rectangle + "}");
+                }
+                if (DEBUG_RECTANGLE_REQUESTED) {
+                    Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
+                }
+                final Message m = PooledLambda.obtainMessage(
+                        mCallbacks::onRectangleOnScreenRequested, displayId, rectangle.left,
+                        rectangle.top, rectangle.right, rectangle.bottom);
+                mHandler.sendMessage(m);
+            }
+        }
     }
 
     private static final class AccessibilityTracing {
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index d736ede..30cd3c4 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.TaskInfo;
@@ -33,12 +34,13 @@
 public abstract class ActivityInterceptorCallback {
     /**
      * Intercept the launch intent based on various signals. If an interception happened, returns
-     * a new/existing non-null {@link Intent} which may redirect to another activity.
+     * a new/existing non-null {@link ActivityInterceptResult} which may redirect to another
+     * activity or with new {@link ActivityOptions}.
      *
-     * @return null if no interception occurred, or a non-null intent which replaces the
-     * existing intent.
+     * @return null if no interception occurred, or a non-null result which replaces the existing
+     * intent and activity options.
      */
-    public abstract @Nullable Intent intercept(ActivityInterceptorInfo info);
+    public abstract @Nullable ActivityInterceptResult intercept(ActivityInterceptorInfo info);
 
     /**
      * Called when an activity is successfully launched.
@@ -108,4 +110,19 @@
             this.checkedOptions = checkedOptions;
         }
     }
+
+    /**
+     * Data class for storing the intercept result.
+     */
+    public static final class ActivityInterceptResult {
+        @NonNull public final Intent intent;
+        @NonNull public final ActivityOptions activityOptions;
+
+        public ActivityInterceptResult(
+                @NonNull Intent intent,
+                @NonNull ActivityOptions activityOptions) {
+            this.intent = intent;
+            this.activityOptions = activityOptions;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2b28478..447f4be 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -157,6 +157,7 @@
 import static com.android.server.wm.ActivityRecordProto.NUM_INTERESTING_WINDOWS;
 import static com.android.server.wm.ActivityRecordProto.PIP_AUTO_ENTER_ENABLED;
 import static com.android.server.wm.ActivityRecordProto.PROC_ID;
+import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
 import static com.android.server.wm.ActivityRecordProto.STARTING_DISPLAYED;
@@ -258,6 +259,7 @@
 import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ConstrainDisplayApisConfig;
 import android.content.pm.PackageManager;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
@@ -585,6 +587,8 @@
      */
     private CompatDisplayInsets mCompatDisplayInsets;
 
+    private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig;
+
     boolean pendingVoiceInteractionStart;   // Waiting for activity-invoked voice session
     IVoiceInteractionSession voiceSession;  // Voice interaction session for this activity
 
@@ -1153,8 +1157,10 @@
             if (info.configChanges != 0) {
                 pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
             }
-            pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis());
-            pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis());
+            pw.println(prefix + "neverSandboxDisplayApis=" + info.neverSandboxDisplayApis(
+                    sConstrainDisplayApisConfig));
+            pw.println(prefix + "alwaysSandboxDisplayApis=" + info.alwaysSandboxDisplayApis(
+                    sConstrainDisplayApisConfig));
         }
         if (mLastParentBeforePip != null) {
             pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
@@ -1731,6 +1737,10 @@
             info.windowLayout.windowLayoutAffinity =
                     uid + ":" + info.windowLayout.windowLayoutAffinity;
         }
+        // Initialize once, when we know all system services are available.
+        if (sConstrainDisplayApisConfig == null) {
+            sConstrainDisplayApisConfig = new ConstrainDisplayApisConfig();
+        }
         stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0;
         nonLocalizedLabel = aInfo.nonLocalizedLabel;
         labelRes = aInfo.labelRes;
@@ -7330,8 +7340,8 @@
                                 + "should create compatDisplayInsets = %s",
                         getUid(),
                         mTmpBounds,
-                        info.neverSandboxDisplayApis(),
-                        info.alwaysSandboxDisplayApis(),
+                        info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
+                        info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
                         !matchParentBounds(),
                         mCompatDisplayInsets != null,
                         shouldCreateCompatDisplayInsets());
@@ -7892,11 +7902,11 @@
             return false;
         }
         // Never apply sandboxing to an app that should be explicitly excluded from the config.
-        if (info != null && info.neverSandboxDisplayApis()) {
+        if (info.neverSandboxDisplayApis(sConstrainDisplayApisConfig)) {
             return false;
         }
         // Always apply sandboxing to an app that should be explicitly included from the config.
-        if (info != null && info.alwaysSandboxDisplayApis()) {
+        if (info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig)) {
             return true;
         }
         // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it
@@ -8899,6 +8909,9 @@
         proto.write(PIP_AUTO_ENTER_ENABLED, pictureInPictureArgs.isAutoEnterEnabled());
         proto.write(IN_SIZE_COMPAT_MODE, inSizeCompatMode());
         proto.write(MIN_ASPECT_RATIO, getMinAspectRatio());
+        // Only record if max bounds sandboxing is applied, if the caller has the necessary
+        // permission to access the device configs.
+        proto.write(PROVIDES_MAX_BOUNDS, providesMaxBounds());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 352a070..658a17b 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -61,6 +61,7 @@
 import com.android.internal.app.UnlaunchableAppActivity;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService;
+import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult;
 
 /**
  * A class that contains activity intercepting logic for {@link ActivityStarter#startActivityLocked}
@@ -194,11 +195,12 @@
 
         for (int i = 0; i < callbacks.size(); i++) {
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
-            final Intent newIntent = callback.intercept(interceptorInfo);
-            if (newIntent == null) {
+            final ActivityInterceptResult interceptResult = callback.intercept(interceptorInfo);
+            if (interceptResult == null) {
                 continue;
             }
-            mIntent = newIntent;
+            mIntent = interceptResult.intent;
+            mActivityOptions = interceptResult.activityOptions;
             mCallingPid = mRealCallingPid;
             mCallingUid = mRealCallingUid;
             mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 471b4ce..65f9e83 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -72,6 +72,8 @@
 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.ActivityTaskManagerService.ANIMATE;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -1288,7 +1290,7 @@
 
         // This is used to block background activity launch even if the app is still
         // visible to user after user clicking home button.
-        final boolean appSwitchAllowed = mService.getBalAppSwitchesAllowed();
+        final int appSwitchState = mService.getBalAppSwitchesState();
 
         // don't abort if the callingUid has a visible window or is a persistent system process
         final int callingUidProcState = mService.mActiveUids.getUidState(callingUid);
@@ -1301,7 +1303,9 @@
 
         // Normal apps with visible app window will be allowed to start activity if app switching
         // is allowed, or apps like live wallpaper with non app visible window will be allowed.
-        if (((appSwitchAllowed || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
+        final boolean appSwitchAllowedOrFg =
+                appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY;
+        if (((appSwitchAllowedOrFg || mService.mActiveUids.hasNonAppVisibleWindow(callingUid))
                 && callingUidHasAnyVisibleWindow)
                 || isCallingUidPersistentSystemProcess) {
             if (DEBUG_ACTIVITY_STARTS) {
@@ -1430,7 +1434,7 @@
         // don't abort if the callerApp or other processes of that uid are allowed in any way
         if (callerApp != null) {
             // first check the original calling process
-            if (callerApp.areBackgroundActivityStartsAllowed(appSwitchAllowed)) {
+            if (callerApp.areBackgroundActivityStartsAllowed(appSwitchState)) {
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Background activity start allowed: callerApp process (pid = "
                             + callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed");
@@ -1444,7 +1448,7 @@
                 for (int i = uidProcesses.size() - 1; i >= 0; i--) {
                     final WindowProcessController proc = uidProcesses.valueAt(i);
                     if (proc != callerApp
-                            && proc.areBackgroundActivityStartsAllowed(appSwitchAllowed)) {
+                            && proc.areBackgroundActivityStartsAllowed(appSwitchState)) {
                         if (DEBUG_ACTIVITY_STARTS) {
                             Slog.d(TAG,
                                     "Background activity start allowed: process " + proc.getPid()
@@ -1458,7 +1462,7 @@
         // anything that has fallen through would currently be aborted
         Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                 + "; callingUid: " + callingUid
-                + "; appSwitchAllowed: " + appSwitchAllowed
+                + "; appSwitchState: " + appSwitchState
                 + "; isCallingUidForeground: " + isCallingUidForeground
                 + "; callingUidHasAnyVisibleWindow: " + callingUidHasAnyVisibleWindow
                 + "; callingUidProcState: " + DebugUtils.valueToString(ActivityManager.class,
@@ -1668,7 +1672,8 @@
                 }
                 if (newTransition != null) {
                     transitionController.requestStartTransition(newTransition,
-                            mTargetTask, remoteTransition);
+                            mTargetTask == null ? r.getTask() : mTargetTask,
+                            remoteTransition, null /* displayChange */);
                 } else if (started) {
                     // Make the collecting transition wait until this request is ready.
                     transitionController.setReady(r, false);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index e38e9c1..7fa9861 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -126,6 +126,35 @@
     }
 
     /**
+     * Sleep tokens cause the activity manager to put the top activity to sleep.
+     * They are used by components such as dreams that may hide and block interaction
+     * with underlying activities.
+     * The Acquirer provides an interface that encapsulates the underlying work, so the user does
+     * not need to handle the token by him/herself.
+     */
+    public interface SleepTokenAcquirer {
+
+        /**
+         * Acquires a sleep token.
+         * @param displayId The display to apply to.
+         */
+        void acquire(int displayId);
+
+        /**
+         * Releases the sleep token.
+         * @param displayId The display to apply to.
+         */
+        void release(int displayId);
+    }
+
+    /**
+     * Creates a sleep token acquirer for the specified display with the specified tag.
+     *
+     * @param tag A string identifying the purpose (eg. "Dream").
+     */
+    public abstract SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag);
+
+    /**
      * Returns home activity for the specified user.
      *
      * @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e4ed04de..173545c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -505,7 +505,27 @@
      * Whether normal application switches are allowed; a call to {@link #stopAppSwitches()
      * disables this.
      */
-    private volatile boolean mAppSwitchesAllowed = true;
+    private volatile int mAppSwitchesState = APP_SWITCH_ALLOW;
+
+    // The duration of resuming foreground app switch from disallow.
+    private static final long RESUME_FG_APP_SWITCH_MS = 500;
+
+    /** App switch is not allowed. */
+    static final int APP_SWITCH_DISALLOW = 0;
+
+    /** App switch is allowed only if the activity launch was requested by a foreground app. */
+    static final int APP_SWITCH_FG_ONLY = 1;
+
+    /** App switch is allowed. */
+    static final int APP_SWITCH_ALLOW = 2;
+
+    @IntDef({
+            APP_SWITCH_DISALLOW,
+            APP_SWITCH_FG_ONLY,
+            APP_SWITCH_ALLOW,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AppSwitchState {}
 
     /**
      * Last stop app switches time, apps finished before this time cannot start background activity
@@ -667,14 +687,14 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
             POWER_MODE_REASON_START_ACTIVITY,
-            POWER_MODE_REASON_FREEZE_DISPLAY,
+            POWER_MODE_REASON_CHANGE_DISPLAY,
             POWER_MODE_REASON_UNKNOWN_VISIBILITY,
             POWER_MODE_REASON_ALL,
     })
     @interface PowerModeReason {}
 
     static final int POWER_MODE_REASON_START_ACTIVITY = 1 << 0;
-    static final int POWER_MODE_REASON_FREEZE_DISPLAY = 1 << 1;
+    static final int POWER_MODE_REASON_CHANGE_DISPLAY = 1 << 1;
     /** @see UnknownAppVisibilityController */
     static final int POWER_MODE_REASON_UNKNOWN_VISIBILITY = 1 << 2;
     /** This can only be used by {@link #endLaunchPowerMode(int)}.*/
@@ -1250,7 +1270,7 @@
             if (topFocusedRootTask != null && topFocusedRootTask.getTopResumedActivity() != null
                     && topFocusedRootTask.getTopResumedActivity().info.applicationInfo.uid
                     == Binder.getCallingUid()) {
-                mAppSwitchesAllowed = true;
+                mAppSwitchesState = APP_SWITCH_ALLOW;
             }
         }
         return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
@@ -2169,8 +2189,8 @@
     /**
      * Return true if app switching is allowed.
      */
-    boolean getBalAppSwitchesAllowed() {
-        return mAppSwitchesAllowed;
+    @AppSwitchState int getBalAppSwitchesState() {
+        return mAppSwitchesState;
     }
 
     /** Register an {@link AnrController} to control the ANR dialog behavior */
@@ -3691,8 +3711,10 @@
     public void stopAppSwitches() {
         mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "stopAppSwitches");
         synchronized (mGlobalLock) {
-            mAppSwitchesAllowed = false;
+            mAppSwitchesState = APP_SWITCH_DISALLOW;
             mLastStopAppSwitchesTime = SystemClock.uptimeMillis();
+            mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);
+            mH.sendEmptyMessageDelayed(H.RESUME_FG_APP_SWITCH_MSG, RESUME_FG_APP_SWITCH_MS);
         }
     }
 
@@ -3700,7 +3722,8 @@
     public void resumeAppSwitches() {
         mAmInternal.enforceCallingPermission(STOP_APP_SWITCHES, "resumeAppSwitches");
         synchronized (mGlobalLock) {
-            mAppSwitchesAllowed = true;
+            mAppSwitchesState = APP_SWITCH_ALLOW;
+            mH.removeMessages(H.RESUME_FG_APP_SWITCH_MSG);
         }
     }
 
@@ -4418,6 +4441,10 @@
     }
 
     private void updateFontScaleIfNeeded(@UserIdInt int userId) {
+        if (userId != getCurrentUserId()) {
+            return;
+        }
+
         final float scaleFactor = Settings.System.getFloatForUser(mContext.getContentResolver(),
                 FONT_SCALE, 1.0f, userId);
 
@@ -4434,6 +4461,10 @@
     }
 
     private void updateFontWeightAdjustmentIfNeeded(@UserIdInt int userId) {
+        if (userId != getCurrentUserId()) {
+            return;
+        }
+
         final int fontWeightAdjustment =
                 Settings.Secure.getIntForUser(
                         mContext.getContentResolver(),
@@ -4544,25 +4575,17 @@
                 reason);
     }
 
-    /**
-     * Sleep tokens cause the activity manager to put the top activity to sleep.
-     * They are used by components such as dreams that may hide and block interaction
-     * with underlying activities.
-     */
-    final class SleepTokenAcquirer {
+    final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer {
         private final String mTag;
         private final SparseArray<RootWindowContainer.SleepToken> mSleepTokens =
                 new SparseArray<>();
 
-        SleepTokenAcquirer(@NonNull String tag) {
+        SleepTokenAcquirerImpl(@NonNull String tag) {
             mTag = tag;
         }
 
-        /**
-         * Acquires a sleep token.
-         * @param displayId The display to apply to.
-         */
-        void acquire(int displayId) {
+        @Override
+        public void acquire(int displayId) {
             synchronized (mGlobalLock) {
                 if (!mSleepTokens.contains(displayId)) {
                     mSleepTokens.append(displayId,
@@ -4572,11 +4595,8 @@
             }
         }
 
-        /**
-         * Releases the sleep token.
-         * @param displayId The display to apply to.
-         */
-        void release(int displayId) {
+        @Override
+        public void release(int displayId) {
             synchronized (mGlobalLock) {
                 final RootWindowContainer.SleepToken token = mSleepTokens.get(displayId);
                 if (token != null) {
@@ -5204,6 +5224,7 @@
         static final int REPORT_TIME_TRACKER_MSG = 1;
         static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
         static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
+        static final int RESUME_FG_APP_SWITCH_MSG = 4;
 
         static final int FIRST_ACTIVITY_TASK_MSG = 100;
         static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5241,6 +5262,14 @@
                     }
                 }
                 break;
+                case RESUME_FG_APP_SWITCH_MSG: {
+                    synchronized (mGlobalLock) {
+                        if (mAppSwitchesState == APP_SWITCH_DISALLOW) {
+                            mAppSwitchesState = APP_SWITCH_FG_ONLY;
+                        }
+                    }
+                }
+                break;
             }
         }
     }
@@ -5266,6 +5295,12 @@
 
     final class LocalService extends ActivityTaskManagerInternal {
         @Override
+        public SleepTokenAcquirer createSleepTokenAcquirer(@NonNull String tag) {
+            Objects.requireNonNull(tag);
+            return new SleepTokenAcquirerImpl(tag);
+        }
+
+        @Override
         public ComponentName getHomeActivityForUser(int userId) {
             synchronized (mGlobalLock) {
                 final ActivityRecord homeActivity =
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 05b74dd..51c8daf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1387,7 +1387,8 @@
 
             task.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_FRONT,
                     0 /* flags */, task, task /* readyGroupRef */,
-                    options != null ? options.getRemoteTransition() : null);
+                    options != null ? options.getRemoteTransition() : null,
+                    null /* displayChange */);
             reason = reason + " findTaskToMoveToFront";
             boolean reparented = false;
             if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 03d6590..d5abe4f 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1426,21 +1426,21 @@
     }
 
     @TransitionType int getKeyguardTransition() {
-        // In case we unocclude Keyguard and occlude it again, meaning that we never actually
-        // unoccclude/occlude Keyguard, but just run a normal transition.
-        final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
-        if (occludeIndex != -1
-                && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) {
+        if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) {
+            return TRANSIT_KEYGUARD_GOING_AWAY;
+        }
+        final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
+        final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE);
+        // No keyguard related transition requests.
+        if (unoccludeIndex == -1 && occludeIndex == -1) {
             return TRANSIT_NONE;
         }
-
-        for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) {
-            final @TransitionType int transit = mNextAppTransitionRequests.get(i);
-            if (isKeyguardTransit(transit)) {
-                return transit;
-            }
+        // In case we unocclude Keyguard and occlude it again, meaning that we never actually
+        // unoccclude/occlude Keyguard, but just run a normal transition.
+        if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) {
+            return TRANSIT_NONE;
         }
-        return TRANSIT_NONE;
+        return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE;
     }
 
     @TransitionType int getFirstAppTransition() {
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 71a10df..0afd872 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -20,6 +20,8 @@
 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.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
+import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -70,13 +72,13 @@
     }
 
     boolean areBackgroundActivityStartsAllowed(int pid, int uid, String packageName,
-            boolean appSwitchAllowed, boolean isCheckingForFgsStart,
+            int appSwitchState, boolean isCheckingForFgsStart,
             boolean hasActivityInVisibleTask, boolean hasBackgroundActivityStartPrivileges,
             long lastStopAppSwitchesTime, long lastActivityLaunchTime,
             long lastActivityFinishTime) {
         // If app switching is not allowed, we ignore all the start activity grace period
         // exception so apps cannot start itself in onPause() after pressing home button.
-        if (appSwitchAllowed) {
+        if (appSwitchState == APP_SWITCH_ALLOW) {
             // Allow if any activity in the caller has either started or finished very recently, and
             // it must be started or finished after last stop app switches time.
             final long now = SystemClock.uptimeMillis();
@@ -111,7 +113,8 @@
             return true;
         }
         // Allow if the caller has an activity in any foreground task.
-        if (appSwitchAllowed && hasActivityInVisibleTask) {
+        if (hasActivityInVisibleTask
+                && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "[Process(" + pid
                         + ")] Activity start allowed: process has activity in foreground task");
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2e318bc..e80a9b9 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -102,6 +102,7 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
 import static com.android.server.wm.DisplayContentProto.CURRENT_FOCUS;
@@ -166,7 +167,6 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.ColorSpace;
-import android.graphics.GraphicBuffer;
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -224,6 +224,7 @@
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 import android.window.DisplayWindowPolicyController;
 import android.window.IDisplayAreaOrganizer;
+import android.window.TransitionRequestInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -664,7 +665,7 @@
     /** All tokens used to put activities on this root task to sleep (including mOffToken) */
     final ArrayList<RootWindowContainer.SleepToken> mAllSleepTokens = new ArrayList<>();
     /** The token acquirer to put root tasks on the display to sleep */
-    private final ActivityTaskManagerService.SleepTokenAcquirer mOffTokenAcquirer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mOffTokenAcquirer;
 
     private boolean mSleeping;
 
@@ -1422,7 +1423,7 @@
         if (configChanged) {
             mWaitingForConfig = true;
             if (mTransitionController.isShellTransitionsEnabled()) {
-                requestChangeTransitionIfNeeded(changes);
+                requestChangeTransitionIfNeeded(changes, null /* displayChange */);
             } else {
                 mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
             }
@@ -1842,6 +1843,17 @@
         }
     }
 
+    /** Returns {@code true} if the decided new rotation has not applied to configuration yet. */
+    private boolean isRotationChanging() {
+        return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
+    }
+
+    private void startFadeRotationAnimationIfNeeded() {
+        if (isRotationChanging()) {
+            startFadeRotationAnimation(false /* shouldDebounce */);
+        }
+    }
+
     /**
      * Starts the hide animation for the windows which will be rotated seamlessly.
      *
@@ -1995,23 +2007,8 @@
     }
 
     void configureDisplayPolicy() {
-        final int width = mBaseDisplayWidth;
-        final int height = mBaseDisplayHeight;
-        final int shortSize;
-        final int longSize;
-        if (width > height) {
-            shortSize = height;
-            longSize = width;
-        } else {
-            shortSize = width;
-            longSize = height;
-        }
-
-        final int shortSizeDp = shortSize * DENSITY_DEFAULT / mBaseDisplayDensity;
-        final int longSizeDp = longSize * DENSITY_DEFAULT / mBaseDisplayDensity;
-
         mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors();
-        mDisplayRotation.configure(width, height, shortSizeDp, longSizeDp);
+        mDisplayRotation.configure(mBaseDisplayWidth, mBaseDisplayHeight);
     }
 
     /**
@@ -2794,6 +2791,15 @@
                     mIsSizeForced ? mBaseDisplayHeight : newHeight,
                     mIsDensityForced ? mBaseDisplayDensity : newDensity);
 
+            configureDisplayPolicy();
+
+            if (physicalDisplayChanged) {
+                // Reapply the rotation window settings, we are doing this after updating
+                // the screen size and configuring display policy as the rotation depends
+                // on the display size
+                mWmService.mDisplayWindowSettings.applyRotationSettingsToDisplayLocked(this);
+            }
+
             // Real display metrics changed, so we should also update initial values.
             mInitialDisplayWidth = newWidth;
             mInitialDisplayHeight = newHeight;
@@ -3201,11 +3207,8 @@
 
         // Hide the windows which are not significant in rotation animation. So that the windows
         // don't need to block the unfreeze time.
-        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()
-                // Do not fade for freezing without rotation change.
-                && mDisplayRotation.getRotation() != getWindowConfiguration().getRotation()
-                && mFadeRotationAnimationController == null) {
-            startFadeRotationAnimation(false /* shouldDebounce */);
+        if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {
+            startFadeRotationAnimationIfNeeded();
         }
     }
 
@@ -3217,17 +3220,24 @@
      * Requests to start a transition for the display configuration change. The given changes must
      * be non-zero. This method is no-op if the display has been collected.
      */
-    void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes) {
+    void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
         final TransitionController controller = mTransitionController;
         if (controller.isCollecting()) {
+            if (displayChange != null) {
+                throw new IllegalArgumentException("Provided displayChange for non-new transition");
+            }
             if (!controller.isCollecting(this)) {
                 controller.collect(this);
+                startFadeRotationAnimationIfNeeded();
             }
             return;
         }
-        final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, this);
+        final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */,
+                this, this, null /* remoteTransition */, displayChange);
         if (t != null) {
-            if (getRotation() != getWindowConfiguration().getRotation()) {
+            mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
+            if (isRotationChanging()) {
                 mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
                 controller.mTransitionMetricsReporter.associate(t,
                         startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
@@ -4090,8 +4100,7 @@
         // Make IME snapshot as trusted overlay
         InputMonitor.setTrustedOverlayInputInfo(imeSurface, t, getDisplayId(),
                 "IME-snapshot-surface");
-        GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer);
-        t.setBuffer(imeSurface, graphicBuffer);
+        t.setBuffer(imeSurface, buffer);
         t.setColorSpace(mSurfaceControl, ColorSpace.get(ColorSpace.Named.SRGB));
         t.setRelativeLayer(imeSurface, activity.getSurfaceControl(), 1);
         t.setPosition(imeSurface, mInputMethodWindow.getDisplayFrame().left,
@@ -5481,14 +5490,6 @@
         return mMetricsLogger;
     }
 
-    void acquireScreenOffToken(boolean acquire) {
-        if (acquire) {
-            mOffTokenAcquirer.acquire(mDisplayId);
-        } else {
-            mOffTokenAcquirer.release(mDisplayId);
-        }
-    }
-
     void onDisplayChanged() {
         mDisplay.getRealSize(mTmpDisplaySize);
         setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
@@ -5500,9 +5501,9 @@
         final int displayState = mDisplayInfo.state;
         if (displayId != DEFAULT_DISPLAY) {
             if (displayState == Display.STATE_OFF) {
-                acquireScreenOffToken(true /* acquire */);
+                mOffTokenAcquirer.acquire(mDisplayId);
             } else if (displayState == Display.STATE_ON) {
-                acquireScreenOffToken(false /* acquire */);
+                mOffTokenAcquirer.release(mDisplayId);
             }
             ProtoLog.v(WM_DEBUG_LAYER_MIRRORING,
                     "Display %d state is now (%d), so update layer mirroring?",
@@ -5700,7 +5701,7 @@
             mWmService.mDisplayNotificationController.dispatchDisplayChanged(
                     this, getConfiguration());
             if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
-                requestChangeTransitionIfNeeded(changes);
+                requestChangeTransitionIfNeeded(changes, null /* displayChange */);
             }
         }
         return changes;
@@ -5891,6 +5892,13 @@
                 rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
                         notifyClients);
             });
+            if (mTransitionController.isCollecting()
+                    && mWallpaperController.getWallpaperTarget() != null) {
+                // Also update wallpapers so that their requestedVisibility immediately reflects
+                // the changes to activity visibility.
+                // TODO(b/206005136): Move visibleRequested logic up to WindowToken.
+                mWallpaperController.adjustWallpaperWindows();
+            }
         } finally {
             mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();
             mInEnsureActivitiesVisible = false;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7a2a311..4768b27 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -45,7 +45,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -752,10 +751,6 @@
 
     public void setAwake(boolean awake) {
         mAwake = awake;
-        // The screen off token for non-default display is controlled by DisplayContent.
-        if (mDisplayContent.isDefaultDisplay) {
-            mDisplayContent.acquireScreenOffToken(!awake);
-        }
     }
 
     public boolean isAwake() {
@@ -850,20 +845,6 @@
     }
 
     /**
-     * Only trusted overlays are allowed to use FLAG_SLIPPERY.
-     */
-    static int sanitizeFlagSlippery(int flags, int privateFlags, String name) {
-        if ((flags & FLAG_SLIPPERY) == 0) {
-            return flags;
-        }
-        if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
-            return flags;
-        }
-        Slog.w(TAG, "Removing FLAG_SLIPPERY for non-trusted overlay " + name);
-        return flags & ~FLAG_SLIPPERY;
-    }
-
-    /**
      * Sanitize the layout parameters coming from a client.  Allows the policy
      * to do things like ensure that windows of a specific type can't take
      * input focus.
@@ -946,8 +927,6 @@
         } else if (mRoundedCornerWindow == win) {
             mRoundedCornerWindow = null;
         }
-
-        attrs.flags = sanitizeFlagSlippery(attrs.flags, attrs.privateFlags, win.getName());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 3200473..8c8b33f 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -62,6 +62,7 @@
 import android.view.IDisplayWindowRotationCallback;
 import android.view.IWindowManager;
 import android.view.Surface;
+import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.R;
@@ -293,7 +294,7 @@
                 currentUserRes.getBoolean(R.bool.config_allowSeamlessRotationDespiteNavBarMoving);
     }
 
-    void configure(int width, int height, int shortSizeDp, int longSizeDp) {
+    void configure(int width, int height) {
         final Resources res = mContext.getResources();
         if (width > height) {
             mLandscapeRotation = Surface.ROTATION_0;
@@ -508,10 +509,13 @@
         mDisplayContent.setLayoutNeeded();
 
         if (useShellTransitions) {
-            final boolean wasInTransition = mDisplayContent.inTransition();
+            final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
+            final TransitionRequestInfo.DisplayChange change = wasCollecting ? null
+                    : new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
+                            oldRotation, mRotation);
             mDisplayContent.requestChangeTransitionIfNeeded(
-                    ActivityInfo.CONFIG_WINDOW_CONFIGURATION);
-            if (wasInTransition) {
+                    ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
+            if (wasCollecting) {
                 // Use remote-rotation infra since the transition has already been requested
                 // TODO(shell-transitions): Remove this once lifecycle management can cover all
                 //                          rotation cases.
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 6d5abe1..8260fd6 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -265,10 +265,6 @@
         dc.mIsDensityForced = hasDensityOverride;
         dc.mIsSizeForced = hasSizeOverride;
 
-        final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null
-                ? settings.mIgnoreOrientationRequest : false;
-        dc.setIgnoreOrientationRequest(ignoreOrientationRequest);
-
         final boolean ignoreDisplayCutout = settings.mIgnoreDisplayCutout != null
                 ? settings.mIgnoreDisplayCutout : false;
         dc.mIgnoreDisplayCutout = ignoreDisplayCutout;
@@ -288,6 +284,15 @@
         dc.mDontMoveToTop = dontMoveToTop;
     }
 
+    void applyRotationSettingsToDisplayLocked(DisplayContent dc) {
+        final DisplayInfo displayInfo = dc.getDisplayInfo();
+        final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
+
+        final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null
+                ? settings.mIgnoreOrientationRequest : false;
+        dc.setIgnoreOrientationRequest(ignoreOrientationRequest);
+    }
+
     /**
      * Updates settings for the given display after system features are loaded into window manager
      * service, e.g. if this device is PC and if this device supports freeform.
diff --git a/services/core/java/com/android/server/wm/FadeAnimationController.java b/services/core/java/com/android/server/wm/FadeAnimationController.java
index 817b27a..561a070 100644
--- a/services/core/java/com/android/server/wm/FadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeAnimationController.java
@@ -21,7 +21,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.util.ArrayMap;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
 import android.view.animation.Animation;
@@ -38,7 +37,6 @@
 public class FadeAnimationController {
     protected final DisplayContent mDisplayContent;
     protected final Context mContext;
-    protected final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();
 
     public FadeAnimationController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
@@ -78,16 +76,8 @@
             return;
         }
 
-        // We deferred the end of the animation when hiding the token, so we need to end it now that
-        // it's shown again.
-        final SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
-            final Runnable runnable = mDeferredFinishCallbacks.remove(windowToken);
-            if (runnable != null) {
-                runnable.run();
-            }
-        } : null;
         windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
-                show /* hidden */, animationType, finishedCallback);
+                show /* hidden */, animationType, null /* finishedCallback */);
     }
 
     protected FadeAnimationAdapter createAdapter(LocalAnimationAdapter.AnimationSpec animationSpec,
@@ -135,7 +125,7 @@
         };
     }
 
-    protected class FadeAnimationAdapter extends LocalAnimationAdapter {
+    protected static class FadeAnimationAdapter extends LocalAnimationAdapter {
         protected final boolean mShow;
         protected final WindowToken mToken;
 
@@ -149,13 +139,10 @@
 
         @Override
         public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
-            // We defer the end of the hide animation to ensure the tokens stay hidden until
-            // we show them again.
-            if (!mShow) {
-                mDeferredFinishCallbacks.put(mToken, endDeferFinishCallback);
-                return true;
-            }
-            return false;
+            // Defer the finish callback (restore leash) of the hide animation to ensure the token
+            // stay hidden until it needs to show again. Besides, when starting the show animation,
+            // the previous hide animation will be cancelled, so the callback can be ignored.
+            return !mShow;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
index cf36c85..bbda577 100644
--- a/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
+++ b/services/core/java/com/android/server/wm/FadeRotationAnimationController.java
@@ -50,7 +50,7 @@
     /** Whether to use constant zero alpha animation. */
     private boolean mHideImmediately;
 
-    /** Whether this controller is triggered from shell transition. */
+    /** Whether this controller is triggered from shell transition with type CHANGE. */
     private final boolean mIsChangeTransition;
 
     /** Whether the start transaction of the transition is committed (by shell). */
@@ -59,21 +59,30 @@
     /** The list to store the drawn tokens before the rotation animation starts. */
     private ArrayList<WindowToken> mPendingShowTokens;
 
+    /** It is used when the display has rotated, but some windows fade out in old rotation. */
+    private SeamlessRotator mRotator;
+
+    private final int mOriginalRotation;
+    private final boolean mHasScreenRotationAnimation;
+
     public FadeRotationAnimationController(DisplayContent displayContent) {
         super(displayContent);
         mService = displayContent.mWmService;
-        mIsChangeTransition = displayContent.inTransition()
-                && displayContent.mTransitionController.getCollectingTransitionType()
-                == WindowManager.TRANSIT_CHANGE;
+        mOriginalRotation = displayContent.getWindowConfiguration().getRotation();
+        final int transitionType =
+                displayContent.mTransitionController.getCollectingTransitionType();
+        mIsChangeTransition = transitionType == WindowManager.TRANSIT_CHANGE;
+        // Only CHANGE type (rotation animation) needs to wait for the start transaction.
         mIsStartTransactionCommitted = !mIsChangeTransition;
-        mTimeoutRunnable = displayContent.getRotationAnimation() != null
-                || mIsChangeTransition ? () -> {
+        mTimeoutRunnable = displayContent.inTransition() ? () -> {
             synchronized (mService.mGlobalLock) {
                 displayContent.finishFadeRotationAnimationIfPossible();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
             }
         } : null;
-        if (mTimeoutRunnable != null) {
+        mHasScreenRotationAnimation =
+                displayContent.getRotationAnimation() != null || mIsChangeTransition;
+        if (mHasScreenRotationAnimation) {
             // Hide the windows immediately because screen should have been covered by screenshot.
             mHideImmediately = true;
         }
@@ -103,6 +112,19 @@
         }, true /* traverseTopToBottom */);
     }
 
+    @Override
+    public void fadeWindowToken(boolean show, WindowToken windowToken, int animationType) {
+        if (show) {
+            final SurfaceControl leash = mTargetWindowTokens.remove(windowToken);
+            if (leash != null && mRotator != null) {
+                // The leash was unrotated by start transaction of transition. Clear the transform
+                // to reshow the window in current rotation.
+                mRotator.setIdentityMatrix(mDisplayContent.getPendingTransaction(), leash);
+            }
+        }
+        super.fadeWindowToken(show, windowToken, animationType);
+    }
+
     /** Applies show animation on the previously hidden window tokens. */
     void show() {
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
@@ -125,19 +147,23 @@
      * controller is created for normal rotation.
      */
     boolean show(WindowToken token) {
+        if (!isTargetToken(token)) return false;
         if (!mIsStartTransactionCommitted) {
             // The fade-in animation should only start after the screenshot layer is shown by shell.
             // Otherwise the window will be blinking before the rotation animation starts. So store
             // to a pending list and animate them until the transaction is committed.
-            if (mTargetWindowTokens.containsKey(token)) {
-                if (mPendingShowTokens == null) {
-                    mPendingShowTokens = new ArrayList<>();
-                }
-                mPendingShowTokens.add(token);
+            if (mPendingShowTokens == null) {
+                mPendingShowTokens = new ArrayList<>();
             }
+            mPendingShowTokens.add(token);
             return false;
         }
-        if (mTimeoutRunnable != null && mTargetWindowTokens.remove(token) != null) {
+        if (!mHasScreenRotationAnimation && token.mTransitionController.inTransition()) {
+            // Defer showing to onTransitionFinished().
+            return false;
+        }
+        // If the timeout runnable is null (fixed rotation), the case will be handled by show().
+        if (mTimeoutRunnable != null) {
             fadeWindowToken(true /* show */, token, ANIMATION_TYPE_FIXED_TRANSFORM);
             if (mTargetWindowTokens.isEmpty()) {
                 mService.mH.removeCallbacks(mTimeoutRunnable);
@@ -177,6 +203,15 @@
         return mTargetWindowTokens.containsKey(token);
     }
 
+    /**
+     * Whether the insets animation leash should use previous position when running fade out
+     * animation in rotated display.
+     */
+    boolean shouldFreezeInsetsPosition(WindowState w) {
+        return !mHasScreenRotationAnimation && w.mTransitionController.inTransition()
+                && isTargetToken(w.mToken);
+    }
+
     void setOnShowRunnable(Runnable onShowRunnable) {
         mOnShowRunnable = onShowRunnable;
     }
@@ -186,6 +221,22 @@
      * transition starts. And associate transaction callback to consume pending animations.
      */
     void setupStartTransaction(SurfaceControl.Transaction t) {
+        if (!mIsChangeTransition) {
+            // Take OPEN/CLOSE transition type as the example, the non-activity windows need to
+            // fade out in previous rotation while display has rotated to the new rotation, so
+            // their leashes are unrotated with the start transaction.
+            mRotator = new SeamlessRotator(mOriginalRotation,
+                    mDisplayContent.getWindowConfiguration().getRotation(),
+                    mDisplayContent.getDisplayInfo(),
+                    false /* applyFixedTransformationHint */);
+            for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+                final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
+                if (leash != null) {
+                    mRotator.applyTransform(t, leash);
+                }
+            }
+            return;
+        }
         // Hide the windows immediately because a screenshot layer should cover the screen.
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
             final SurfaceControl leash = mTargetWindowTokens.valueAt(i);
@@ -208,9 +259,30 @@
         });
     }
 
+    void onTransitionFinished() {
+        if (mIsChangeTransition) {
+            // With screen rotation animation, the windows are always faded in when they are drawn.
+            // Because if they are drawn fast enough, the fade animation should not be observable.
+            return;
+        }
+        // For other transition types, the fade-in animation runs after the transition to make the
+        // transition animation (e.g. launch activity) look cleaner.
+        for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
+            final WindowToken token = mTargetWindowTokens.keyAt(i);
+            for (int j = token.getChildCount() - 1; j >= 0; j--) {
+                // Only fade in the drawn windows. If the remaining windows are drawn later,
+                // show(WindowToken) will be called to fade in them.
+                if (token.getChildAt(j).isDrawFinishedLw()) {
+                    mDisplayContent.finishFadeRotationAnimation(token);
+                    break;
+                }
+            }
+        }
+    }
+
     @Override
     public Animation getFadeInAnimation() {
-        if (mTimeoutRunnable != null) {
+        if (mHasScreenRotationAnimation) {
             // Use a shorter animation so it is easier to align with screen rotation animation.
             return AnimationUtils.loadAnimation(mContext, R.anim.screen_rotate_0_enter);
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index af91726..a8a9231 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -297,6 +297,14 @@
     }
 
     private Point getWindowFrameSurfacePosition() {
+        if (mControl != null) {
+            final FadeRotationAnimationController fadeController =
+                    mWin.mDisplayContent.getFadeRotationAnimationController();
+            if (fadeController != null && fadeController.shouldFreezeInsetsPosition(mWin)) {
+                // Use previous position because the fade-out animation runs in old rotation.
+                return mControl.getSurfacePosition();
+            }
+        }
         final Rect frame = mWin.getFrame();
         final Point position = new Point();
         mWin.transformFrameToSurfacePosition(frame.left, frame.top, position);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index cabe414..baf7f87 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -75,14 +75,14 @@
     private final SparseArray<KeyguardDisplayState> mDisplayStates = new SparseArray<>();
     private final ActivityTaskManagerService mService;
     private RootWindowContainer mRootWindowContainer;
-    private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+    private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
 
     KeyguardController(ActivityTaskManagerService service,
             ActivityTaskSupervisor taskSupervisor) {
         mService = service;
         mTaskSupervisor = taskSupervisor;
-        mSleepTokenAcquirer = mService.new SleepTokenAcquirer(KEYGUARD_SLEEP_TOKEN_TAG);
+        mSleepTokenAcquirer = mService.new SleepTokenAcquirerImpl(KEYGUARD_SLEEP_TOKEN_TAG);
     }
 
     void setWindowManager(WindowManagerService windowManager) {
@@ -518,10 +518,10 @@
 
         private boolean mRequestDismissKeyguard;
         private final ActivityTaskManagerService mService;
-        private final ActivityTaskManagerService.SleepTokenAcquirer mSleepTokenAcquirer;
+        private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
 
         KeyguardDisplayState(ActivityTaskManagerService service, int displayId,
-                ActivityTaskManagerService.SleepTokenAcquirer acquirer) {
+                ActivityTaskManagerInternal.SleepTokenAcquirer acquirer) {
             mService = service;
             mDisplayId = displayId;
             mSleepTokenAcquirer = acquirer;
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 94a175c..8a2d116 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -251,15 +251,47 @@
      */
     boolean activityBlockedFromFinish(ActivityRecord activity) {
         final Task task = activity.getTask();
-        if (activity == task.getRootActivity()
-                && activity == task.getTopNonFinishingActivity()
-                && task.mLockTaskAuth != LOCK_TASK_AUTH_LAUNCHABLE_PRIV
-                && isRootTask(task)) {
-            Slog.i(TAG, "Not finishing task in lock task mode");
-            showLockTaskToast();
-            return true;
+        if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV || !isRootTask(task)) {
+            return false;
         }
-        return false;
+
+        final ActivityRecord taskTop = task.getTopNonFinishingActivity();
+        final ActivityRecord taskRoot = task.getRootActivity();
+        // If task has more than one Activity, verify if there's only adjacent TaskFragments that
+        // should be finish together in the Task.
+        if (activity != taskRoot || activity != taskTop) {
+            final TaskFragment taskFragment = activity.getTaskFragment();
+            final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+            if (taskFragment.asTask() != null
+                    || !taskFragment.isDelayLastActivityRemoval()
+                    || adjacentTaskFragment == null) {
+                // Don't block activity from finishing if the TaskFragment don't have any adjacent
+                // TaskFragment, or it won't finish together with its adjacent TaskFragment.
+                return false;
+            }
+
+            final boolean hasOtherActivityInTaskFragment =
+                    taskFragment.getActivity(a -> !a.finishing && a != activity) != null;
+            if (hasOtherActivityInTaskFragment) {
+                // Don't block activity from finishing if there's other Activity in the same
+                // TaskFragment.
+                return false;
+            }
+
+            final boolean hasOtherActivityInTask = task.getActivity(a -> !a.finishing
+                    && a != activity && a.getTaskFragment() != adjacentTaskFragment) != null;
+            if (hasOtherActivityInTask) {
+                // Do not block activity from finishing if there are another running activities
+                // after the current and adjacent TaskFragments are removed. Note that we don't
+                // check activities in adjacent TaskFragment because it will be finished together
+                // with TaskFragment regardless of numbers of activities.
+                return false;
+            }
+        }
+
+        Slog.i(TAG, "Not finishing task in lock task mode");
+        showLockTaskToast();
+        return true;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
index af8293a..80f2ab6 100644
--- a/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
+++ b/services/core/java/com/android/server/wm/NavBarFadeAnimationController.java
@@ -95,14 +95,6 @@
             } else {
                 fadeAnim.run();
             }
-        } else {
-            // If fade rotation animation is running and controlling the nav bar, make sure we empty
-            // the mDeferredFinishCallbacks and defer the runnable until fade rotation animation
-            // finishes.
-            final Runnable runnable = mDeferredFinishCallbacks.remove(mNavigationBar.mToken);
-            if (runnable != null) {
-                controller.setOnShowRunnable(runnable);
-            }
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index b54208d..1da0fe7 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -243,7 +243,8 @@
             int oldRotation, int newRotation) {
         final Rect bounds = mDestRotatedBounds;
         final PictureInPictureSurfaceTransaction pipTx = mPipTransaction;
-        if (bounds == null && pipTx == null) {
+        final boolean emptyPipPositionTx = pipTx == null || pipTx.mPosition == null;
+        if (bounds == null && emptyPipPositionTx) {
             return;
         }
         final TaskDisplayArea taskArea = mDisplayContent.getDefaultTaskDisplayArea();
@@ -255,25 +256,27 @@
         mDestRotatedBounds = null;
         mPipTransaction = null;
         final Rect areaBounds = taskArea.getBounds();
-        if (pipTx != null) {
+        if (!emptyPipPositionTx) {
             // The transaction from recents animation is in old rotation. So the position needs to
             // be rotated.
-            float dx = pipTx.mPositionX;
-            float dy = pipTx.mPositionY;
+            float dx = pipTx.mPosition.x;
+            float dy = pipTx.mPosition.y;
             final Matrix matrix = pipTx.getMatrix();
             if (pipTx.mRotation == 90) {
-                dx = pipTx.mPositionY;
-                dy = areaBounds.right - pipTx.mPositionX;
+                dx = pipTx.mPosition.y;
+                dy = areaBounds.right - pipTx.mPosition.x;
                 matrix.postRotate(-90);
             } else if (pipTx.mRotation == -90) {
-                dx = areaBounds.bottom - pipTx.mPositionY;
-                dy = pipTx.mPositionX;
+                dx = areaBounds.bottom - pipTx.mPosition.y;
+                dy = pipTx.mPosition.x;
                 matrix.postRotate(90);
             }
             matrix.postTranslate(dx, dy);
             final SurfaceControl leash = pinnedTask.getSurfaceControl();
-            t.setMatrix(leash, matrix, new float[9])
-                    .setCornerRadius(leash, pipTx.mCornerRadius);
+            t.setMatrix(leash, matrix, new float[9]);
+            if (pipTx.hasCornerRadiusSet()) {
+                t.setCornerRadius(leash, pipTx.mCornerRadius);
+            }
             Slog.i(TAG, "Seamless rotation PiP tx=" + pipTx + " pos=" + dx + "," + dy);
             return;
         }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 38e3e3a..f97a48b 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -263,13 +263,6 @@
                     "finish(%b): mCanceled=%b", moveHomeToTop, mCanceled);
             final long token = Binder.clearCallingIdentity();
             try {
-                synchronized (mService.getWindowManagerLock()) {
-                    // Remove all new task targets.
-                    for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
-                        removeTaskInternal(mPendingNewTaskTargets.get(i));
-                    }
-                }
-
                 // Note, the callback will handle its own synchronization, do not lock on WM lock
                 // prior to calling the callback
                 mCallbacks.onAnimationFinished(moveHomeToTop
@@ -759,7 +752,7 @@
         // the task-id with the leaf id.
         final Task leafTask = task.getTopLeafTask();
         int taskId = leafTask.mTaskId;
-        TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
+        TaskAnimationAdapter adapter = addAnimation(task,
                 !recentTaskIds.get(taskId), true /* hidden */, finishedCallback);
         mPendingNewTaskTargets.add(taskId);
         return adapter.createRemoteAnimationTarget(taskId);
@@ -1012,6 +1005,7 @@
             taskAdapter.onCleanup();
         }
         // Should already be empty, but clean-up pending task-appears in-case they weren't sent.
+        mPendingNewTaskTargets.clear();
         mPendingTaskAppears.clear();
 
         for (int i = mPendingWallpaperAnimations.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index b63843d..7bddb62 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -164,7 +164,19 @@
             return 0;
         }
 
-        return w.mAttrs.preferredMinDisplayRefreshRate;
+        if (w.mAttrs.preferredMinDisplayRefreshRate > 0) {
+            return w.mAttrs.preferredMinDisplayRefreshRate;
+        }
+
+        String packageName = w.getOwningPackage();
+        // If app is using Camera, we set both the min and max refresh rate to the camera's
+        // preferred refresh rate to make sure we don't end up with a refresh rate lower
+        // than the camera capture rate, which will lead to dropping camera frames.
+        if (mNonHighRefreshRatePackages.contains(packageName)) {
+            return mLowRefreshRateMode.getRefreshRate();
+        }
+
+        return 0;
     }
 
     float getPreferredMaxRefreshRate(WindowState w) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 117b22a..c7b13eb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -225,7 +225,7 @@
     private static final String DISPLAY_OFF_SLEEP_TOKEN_TAG = "Display-off";
 
     /** The token acquirer to put root tasks on the displays to sleep */
-    final ActivityTaskManagerService.SleepTokenAcquirer mDisplayOffTokenAcquirer;
+    final ActivityTaskManagerInternal.SleepTokenAcquirer mDisplayOffTokenAcquirer;
 
     /**
      * The modes which affect which tasks are returned when calling
@@ -470,7 +470,7 @@
         mService = service.mAtmService;
         mTaskSupervisor = mService.mTaskSupervisor;
         mTaskSupervisor.mRootWindowContainer = this;
-        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirer(DISPLAY_OFF_SLEEP_TOKEN_TAG);
+        mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
     }
 
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index 4cc369f..c20b858 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -20,7 +20,6 @@
 import static android.view.Surface.ROTATION_90;
 
 import android.graphics.Matrix;
-import android.os.IBinder;
 import android.view.DisplayInfo;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
@@ -73,7 +72,7 @@
      * global display rotation.
      */
     public void unrotate(Transaction transaction, WindowContainer win) {
-        transaction.setMatrix(win.getSurfaceControl(), mTransform, mFloat9);
+        applyTransform(transaction, win.getSurfaceControl());
         // WindowState sets the position of the window so transform the position and update it.
         final float[] winSurfacePos = {win.mLastSurfacePosition.x, win.mLastSurfacePosition.y};
         mTransform.mapPoints(winSurfacePos);
@@ -83,6 +82,10 @@
         }
     }
 
+    void applyTransform(Transaction t, SurfaceControl sc) {
+        t.setMatrix(sc, mTransform, mFloat9);
+    }
+
     /**
      * Returns the rotation of the display before it started rotating.
      *
@@ -106,14 +109,17 @@
             return;
         }
 
-        mTransform.reset();
-        t.setMatrix(win.mSurfaceControl, mTransform, mFloat9);
+        setIdentityMatrix(t, win.mSurfaceControl);
         t.setPosition(win.mSurfaceControl, win.mLastSurfacePosition.x, win.mLastSurfacePosition.y);
         if (mApplyFixedTransformHint) {
             t.unsetFixedTransformHint(win.mSurfaceControl);
         }
     }
 
+    void setIdentityMatrix(Transaction t, SurfaceControl sc) {
+        t.setMatrix(sc, Matrix.IDENTITY_MATRIX, mFloat9);
+    }
+
     public void dump(PrintWriter pw) {
         pw.print("{old="); pw.print(mOldRotation); pw.print(", new="); pw.print(mNewRotation);
         pw.print("}");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f0b55cb..3e55811 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1440,11 +1440,11 @@
     }
 
     /** Called when an {@link ActivityRecord} is added as a descendant */
-    void onDescendantActivityAdded(boolean hadChild, int activityType, ActivityRecord r) {
+    void onDescendantActivityAdded(boolean hadActivity, int activityType, ActivityRecord r) {
         warnForNonLeafTask("onDescendantActivityAdded");
 
         // Only set this based on the first activity
-        if (!hadChild) {
+        if (!hadActivity) {
             if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) {
                 // Normally non-standard activity type for the activity record will be set when the
                 // object is created, however we delay setting the standard application type until
@@ -3115,14 +3115,6 @@
     }
 
     @Override
-    boolean fillsParent() {
-        // From the perspective of policy, we still want to report that this task fills parent
-        // in fullscreen windowing mode even it doesn't match parent bounds because there will be
-        // letterbox around its real content.
-        return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
-    }
-
-    @Override
     void forAllLeafTasks(Consumer<Task> callback, boolean traverseTopToBottom) {
         final int count = mChildren.size();
         boolean isLeafTask = true;
@@ -4534,14 +4526,15 @@
             }
             super.setWindowingMode(windowingMode);
 
-            // Try reparent pinned activity back to its original task after onConfigurationChanged
-            // cascade finishes. This is done on Task level instead of
-            // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
-            // we set final windowing mode on the ActivityRecord first and then on its Task when
-            // the exit PiP transition finishes. Meanwhile, the exit transition is always
-            // performed on its original task, reparent immediately in ActivityRecord breaks it.
-            if (currentMode == WINDOWING_MODE_PINNED) {
-                if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+            if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) {
+                // Try reparent pinned activity back to its original task after
+                // onConfigurationChanged cascade finishes. This is done on Task level instead of
+                // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit
+                // PiP, we set final windowing mode on the ActivityRecord first and then on its
+                // Task when the exit PiP transition finishes. Meanwhile, the exit transition is
+                // always performed on its original task, reparent immediately in ActivityRecord
+                // breaks it.
+                if (topActivity.getLastParentBeforePip() != null) {
                     // Do not reparent if the pinned task is in removal, indicated by the
                     // force hidden flag.
                     if (!isForceHidden()) {
@@ -4554,6 +4547,11 @@
                         }
                     }
                 }
+                // Resume app-switches-allowed flag when exiting from pinned mode since
+                // it does not follow the ActivityStarter path.
+                if (topActivity.shouldBeVisible()) {
+                    mAtmService.resumeAppSwitches();
+                }
             }
 
             if (creating) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 59a5cdf..97cb512 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -101,6 +101,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -239,7 +240,7 @@
     /**
      * 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 other actions or animations.
+     * opportunity to perform animations and finishing the adjacent TaskFragment.
      */
     private boolean mDelayLastActivityRemoval;
 
@@ -1666,8 +1667,8 @@
         boolean isAddingActivity = child.asActivityRecord() != null;
         final Task task = isAddingActivity ? getTask() : null;
 
-        // If this task had any child before we added this one.
-        boolean taskHadChild = task != null && task.hasChild();
+        // If this task had any activity before we added this one.
+        boolean taskHadActivity = task != null && task.getActivity(Objects::nonNull) != null;
         // getActivityType() looks at the top child, so we need to read the type before adding
         // a new child in case the new child is on top and UNDEFINED.
         final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
@@ -1676,7 +1677,7 @@
 
         if (isAddingActivity && task != null) {
             child.asActivityRecord().inHistory = true;
-            task.onDescendantActivityAdded(taskHadChild, activityType, child.asActivityRecord());
+            task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord());
         }
     }
 
@@ -2186,14 +2187,13 @@
     TaskFragmentInfo getTaskFragmentInfo() {
         List<IBinder> childActivities = new ArrayList<>();
         for (int i = 0; i < getChildCount(); i++) {
-            WindowContainer wc = getChildAt(i);
-            if (mTaskFragmentOrganizerUid != INVALID_UID
-                    && wc.asActivityRecord() != null
-                    && wc.asActivityRecord().info.processName.equals(
-                            mTaskFragmentOrganizerProcessName)
-                    && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) {
+            final WindowContainer wc = getChildAt(i);
+            final ActivityRecord ar = wc.asActivityRecord();
+            if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null
+                    && ar.info.processName.equals(mTaskFragmentOrganizerProcessName)
+                    && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) {
                 // Only includes Activities that belong to the organizer process for security.
-                childActivities.add(wc.asActivityRecord().token);
+                childActivities.add(ar.token);
             }
         }
         final Point positionInParent = new Point();
@@ -2342,6 +2342,14 @@
         return true;
     }
 
+    @Override
+    boolean fillsParent() {
+        // From the perspective of policy, we still want to report that this task fills parent
+        // in fullscreen windowing mode even it doesn't match parent bounds because there will be
+        // letterbox around its real content.
+        return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
+    }
+
     boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
             boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
         boolean printed = false;
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 29c27f9..c7fdefc 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -24,6 +24,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -579,4 +580,26 @@
                         event.mException);
         }
     }
+
+    // TODO(b/204399167): change to push the embedded state to the client side
+    @Override
+    public boolean isActivityEmbedded(IBinder activityToken) {
+        synchronized (mGlobalLock) {
+            final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
+            if (activity == null) {
+                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;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3974747..c7c3bb6 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -143,6 +143,9 @@
     /** The final animation targets derived from participants after promotion. */
     private ArraySet<WindowContainer> mTargets = null;
 
+    /** The main display running this transition. */
+    private DisplayContent mTargetDisplay;
+
     /**
      * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
      * the transition animation.
@@ -365,6 +368,13 @@
                 t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
                 t.setCornerRadius(targetLeash, 0);
                 t.setShadowRadius(targetLeash, 0);
+                // The bounds sent to the transition is always a real bounds. This means we lose
+                // information about "null" bounds (inheriting from parent). Core will fix-up
+                // non-organized window surface bounds; however, since Core can't touch organized
+                // surfaces, add the "inherit from parent" restoration here.
+                if (target.isOrganized() && target.matchParentBounds()) {
+                    t.setWindowCrop(targetLeash, -1, -1);
+                }
                 displays.add(target.getDisplayContent());
             }
         }
@@ -466,6 +476,12 @@
                     mController.mAtm.mRootWindowContainer.getDisplayContent(mRecentsDisplayId);
             dc.getInputMonitor().setActiveRecents(null /* activity */, null /* layer */);
         }
+
+        final FadeRotationAnimationController fadeRotationController =
+                mTargetDisplay.getFadeRotationAnimationController();
+        if (fadeRotationController != null) {
+            fadeRotationController.onTransitionFinished();
+        }
     }
 
     void abort() {
@@ -507,6 +523,7 @@
             }
         }
         if (dc == null) dc = mController.mAtm.mRootWindowContainer.getDefaultDisplay();
+        mTargetDisplay = dc;
 
         if (mState == STATE_ABORT) {
             mController.abort(this);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 99dfe13..ffe1462 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -24,6 +24,8 @@
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 
+import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -245,7 +247,7 @@
             @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
             @NonNull WindowContainer readyGroupRef) {
         return requestTransitionIfNeeded(type, flags, trigger, readyGroupRef,
-                null /* remoteTransition */);
+                null /* remoteTransition */, null /* displayChange */);
     }
 
     private static boolean isExistenceType(@WindowManager.TransitionType int type) {
@@ -262,12 +264,16 @@
     @Nullable
     Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
             @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
-            @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition) {
+            @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
         if (mTransitionPlayer == null) {
             return null;
         }
         Transition newTransition = null;
         if (isCollecting()) {
+            if (displayChange != null) {
+                throw new IllegalArgumentException("Provided displayChange for a non-new request");
+            }
             // Make the collecting transition wait until this request is ready.
             mCollectingTransition.setReady(readyGroupRef, false);
             if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
@@ -276,7 +282,7 @@
             }
         } else {
             newTransition = requestStartTransition(createTransition(type, flags),
-                    trigger != null ? trigger.asTask() : null, remoteTransition);
+                    trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
         }
         if (trigger != null) {
             if (isExistenceType(type)) {
@@ -291,7 +297,8 @@
     /** Asks the transition player (shell) to start a created but not yet started transition. */
     @NonNull
     Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
-            @Nullable RemoteTransition remoteTransition) {
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange) {
         try {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Requesting StartTransition: %s", transition);
@@ -301,7 +308,7 @@
                 startTask.fillTaskInfo(info);
             }
             mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
-                    transition.mType, info, remoteTransition));
+                    transition.mType, info, remoteTransition, displayChange));
         } catch (RemoteException e) {
             Slog.e(TAG, "Error requesting transition", e);
             transition.start();
@@ -315,7 +322,7 @@
         if (wc.isVisibleRequested()) {
             if (!isCollecting()) {
                 requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
-                        wc.asTask(), null /* remoteTransition */);
+                        wc.asTask(), null /* remoteTransition */, null /* displayChange */);
             }
             collectExistenceChange(wc);
         } else {
@@ -382,6 +389,8 @@
     void finishTransition(@NonNull IBinder token) {
         // It is usually a no-op but make sure that the metric consumer is removed.
         mTransitionMetricsReporter.reportAnimationStart(token, 0 /* startTime */);
+        // It is a no-op if the transition did not change the display.
+        mAtm.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
         final Transition record = Transition.fromBinder(token);
         if (record == null || !mPlayingTransitions.contains(record)) {
             Slog.e(TAG, "Trying to finish a non-playing transition " + token);
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index cc52713..7956a11 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -173,7 +173,7 @@
 
     @VisibleForTesting
     class WindowContextListenerImpl implements WindowContainerListener {
-        @NonNull private final IBinder mClientToken;
+        @NonNull private final IWindowToken mClientToken;
         private final int mOwnerUid;
         @NonNull private WindowContainer<?> mContainer;
         /**
@@ -193,7 +193,7 @@
 
         private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container,
                 int ownerUid, @WindowType int type, @Nullable Bundle options) {
-            mClientToken = clientToken;
+            mClientToken = IWindowToken.Stub.asInterface(clientToken);
             mContainer = Objects.requireNonNull(container);
             mOwnerUid = ownerUid;
             mType = type;
@@ -205,7 +205,7 @@
                 mDeathRecipient = deathRecipient;
             } catch (RemoteException e) {
                 ProtoLog.e(WM_ERROR, "Could not register window container listener token=%s, "
-                        + "container=%s", mClientToken, mContainer);
+                        + "container=%s", clientToken, mContainer);
             }
         }
 
@@ -228,17 +228,17 @@
         }
 
         private void register() {
+            final IBinder token = mClientToken.asBinder();
             if (mDeathRecipient == null) {
-                throw new IllegalStateException("Invalid client token: " + mClientToken);
+                throw new IllegalStateException("Invalid client token: " + token);
             }
-            mListeners.putIfAbsent(mClientToken, this);
+            mListeners.putIfAbsent(token, this);
             mContainer.registerWindowContainerListener(this);
-            reportConfigToWindowTokenClient();
         }
 
         private void unregister() {
             mContainer.unregisterWindowContainerListener(this);
-            mListeners.remove(mClientToken);
+            mListeners.remove(mClientToken.asBinder());
         }
 
         private void clear() {
@@ -258,19 +258,24 @@
 
         private void reportConfigToWindowTokenClient() {
             if (mDeathRecipient == null) {
-                throw new IllegalStateException("Invalid client token: " + mClientToken);
+                throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder());
+            }
+            final DisplayContent dc = mContainer.getDisplayContent();
+            if (!dc.isReady()) {
+                // Do not report configuration when booting. The latest configuration will be sent
+                // when WindowManagerService#displayReady().
+                return;
             }
             // If the display of window context associated window container is suspended, don't
             // report the configuration update. Note that we still dispatch the configuration update
             // to WindowProviderService to make it compatible with Service#onConfigurationChanged.
             // Service always receives #onConfigurationChanged callback regardless of display state.
-            if (!isWindowProviderService(mOptions)
-                    && isSuspendedState(mContainer.getDisplayContent().getDisplayInfo().state)) {
+            if (!isWindowProviderService(mOptions) && isSuspendedState(dc.getDisplayInfo().state)) {
                 mHasPendingConfiguration = true;
                 return;
             }
             final Configuration config = mContainer.getConfiguration();
-            final int displayId = mContainer.getDisplayContent().getDisplayId();
+            final int displayId = dc.getDisplayId();
             if (mLastReportedConfig == null) {
                 mLastReportedConfig = new Configuration();
             }
@@ -282,9 +287,8 @@
             mLastReportedConfig.setTo(config);
             mLastReportedDisplay = displayId;
 
-            IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken);
             try {
-                windowTokenClient.onConfigurationChanged(config, displayId);
+                mClientToken.onConfigurationChanged(config, displayId);
             } catch (RemoteException e) {
                 ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client.");
             }
@@ -294,7 +298,7 @@
         @Override
         public void onRemoved() {
             if (mDeathRecipient == null) {
-                throw new IllegalStateException("Invalid client token: " + mClientToken);
+                throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder());
             }
             final WindowToken windowToken = mContainer.asWindowToken();
             if (windowToken != null && windowToken.isFromClient()) {
@@ -312,9 +316,8 @@
                 }
             }
             mDeathRecipient.unlinkToDeath();
-            IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(mClientToken);
             try {
-                windowTokenClient.onWindowTokenRemoved();
+                mClientToken.onWindowTokenRemoved();
             } catch (RemoteException e) {
                 ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client.");
             }
@@ -323,7 +326,7 @@
 
         @Override
         public String toString() {
-            return "WindowContextListenerImpl{clientToken=" + mClientToken + ", "
+            return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", "
                     + "container=" + mContainer + "}";
         }
 
@@ -337,11 +340,11 @@
             }
 
             void linkToDeath() throws RemoteException {
-                mClientToken.linkToDeath(this, 0);
+                mClientToken.asBinder().linkToDeath(this, 0);
             }
 
             void unlinkToDeath() {
-                mClientToken.unlinkToDeath(this, 0);
+                mClientToken.asBinder().unlinkToDeath(this, 0);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 34cb5ae..62c674b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -104,6 +104,32 @@
         void logTrace(String where, long loggingTypeFlags, String callingParams,
                 byte[] a11yDump, int callingUid, StackTraceElement[] callStack, long timeStamp,
                 int processId, long threadId, Set<String> ignoreStackEntries);
+
+        /**
+         * Set by the accessibility related modules which want to listen the event dispatched from
+         * window manager. Accessibility modules can use these callbacks to handle some display
+         * manipulations.
+         * @param callbacks The callbacks to invoke.
+         */
+        void setUiChangesForAccessibilityCallbacks(UiChangesForAccessibilityCallbacks callbacks);
+
+        /**
+         * This interface is used by window manager to dispatch some ui change events which may
+         * affect the screen accessibility features.
+         */
+        interface UiChangesForAccessibilityCallbacks {
+            /**
+             * Called when an application requests a rectangle focus on the screen.
+             *
+             * @param displayId The logical display id
+             * @param left The rectangle left.
+             * @param top The rectangle top.
+             * @param right The rectangle right.
+             * @param bottom The rectangle bottom.
+             */
+            void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
+                    int bottom);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 23f9c58..86775f6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -108,7 +108,7 @@
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_FREEZE_DISPLAY;
+import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
@@ -1652,6 +1652,7 @@
 
             final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
             displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
+            attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
             win.setRequestedVisibilities(requestedVisibilities);
 
             res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
@@ -2143,11 +2144,13 @@
     }
 
     public void onRectangleOnScreenRequested(IBinder token, Rect rectangle) {
+        final AccessibilityController.AccessibilityControllerInternalImpl a11yControllerInternal =
+                AccessibilityController.getAccessibilityControllerInternal(this);
         synchronized (mGlobalLock) {
-            if (mAccessibilityController.hasCallbacks()) {
+            if (a11yControllerInternal.hasWindowManagerEventDispatcher()) {
                 WindowState window = mWindowMap.get(token);
                 if (window != null) {
-                    mAccessibilityController.onRectangleOnScreenRequested(
+                    a11yControllerInternal.onRectangleOnScreenRequested(
                             window.getDisplayId(), rectangle);
                 }
             }
@@ -2207,6 +2210,7 @@
             if (attrs != null) {
                 displayPolicy.adjustWindowParamsLw(win, attrs);
                 win.mToken.adjustWindowParams(win, attrs);
+                attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
                 int disableFlags =
                         (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility) & DISABLE_MASK;
                 if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) {
@@ -5849,7 +5853,7 @@
         mScreenFrozenLock.acquire();
         // Apply launch power mode to reduce screen frozen time because orientation change may
         // relaunch activity and redraw windows. This may also help speed up user switching.
-        mAtmService.startLaunchPowerMode(POWER_MODE_REASON_FREEZE_DISPLAY);
+        mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
 
         mDisplayFrozen = true;
         mDisplayFreezeTime = SystemClock.elapsedRealtime();
@@ -5993,7 +5997,7 @@
         if (configChanged) {
             displayContent.sendNewConfiguration();
         }
-        mAtmService.endLaunchPowerMode(POWER_MODE_REASON_FREEZE_DISPLAY);
+        mAtmService.endLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
         mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN);
     }
 
@@ -8220,6 +8224,23 @@
     }
 
     /**
+     * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
+     */
+    private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
+        if ((flags & FLAG_SLIPPERY) == 0) {
+            return flags;
+        }
+        final int permissionResult = mContext.checkPermission(
+                    android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid);
+        if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+            Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName
+                    + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission");
+            return flags & ~FLAG_SLIPPERY;
+        }
+        return flags;
+    }
+
+    /**
      * Assigns an InputChannel to a SurfaceControl and configures it to receive
      * touch input according to it's on-screen geometry.
      *
@@ -8258,7 +8279,7 @@
         h.setWindowToken(window);
         h.name = name;
 
-        flags = DisplayPolicy.sanitizeFlagSlippery(flags, privateFlags, name);
+        flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
 
         final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
                 | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 525d84be..79dcbcb 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -33,6 +33,7 @@
 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_START_ACTIVITY_IN_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
@@ -79,6 +80,8 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.function.pooled.PooledConsumer;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -785,6 +788,22 @@
                         null /* requiredPermission */, options);
                 break;
             }
+            case HIERARCHY_OP_TYPE_START_SHORTCUT: {
+                final Bundle launchOpts = hop.getLaunchOptions();
+                final String callingPackage = launchOpts.getString(
+                        WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE);
+                launchOpts.remove(
+                        WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_SHORTCUT_CALLING_PACKAGE);
+
+                final LauncherAppsServiceInternal launcherApps = LocalServices.getService(
+                        LauncherAppsServiceInternal.class);
+
+                launcherApps.startShortcut(caller.mUid, caller.mPid, callingPackage,
+                        hop.getShortcutInfo().getPackage(), null /* default featureId */,
+                        hop.getShortcutInfo().getId(), null /* sourceBounds */, launchOpts,
+                        hop.getShortcutInfo().getUserId());
+                break;
+            }
             case HIERARCHY_OP_TYPE_REPARENT_CHILDREN: {
                 final WindowContainer oldParent = WindowContainer.fromBinder(hop.getContainer());
                 final WindowContainer newParent = hop.getNewParent() != null
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 1cfbe07..3ccb06c 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -512,19 +512,19 @@
      */
     @HotPath(caller = HotPath.START_SERVICE)
     public boolean areBackgroundFgsStartsAllowed() {
-        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesAllowed(),
+        return areBackgroundActivityStartsAllowed(mAtm.getBalAppSwitchesState(),
                 true /* isCheckingForFgsStart */);
     }
 
-    boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed) {
-        return areBackgroundActivityStartsAllowed(appSwitchAllowed,
+    boolean areBackgroundActivityStartsAllowed(int appSwitchState) {
+        return areBackgroundActivityStartsAllowed(appSwitchState,
                 false /* isCheckingForFgsStart */);
     }
 
-    private boolean areBackgroundActivityStartsAllowed(boolean appSwitchAllowed,
+    private boolean areBackgroundActivityStartsAllowed(int appSwitchState,
             boolean isCheckingForFgsStart) {
         return mBgLaunchController.areBackgroundActivityStartsAllowed(mPid, mUid, mInfo.packageName,
-                appSwitchAllowed, isCheckingForFgsStart, hasActivityInVisibleTask(),
+                appSwitchState, isCheckingForFgsStart, hasActivityInVisibleTask(),
                 mInstrumentingWithBackgroundActivityStartPrivileges,
                 mAtm.getLastStopAppSwitchesTime(),
                 mLastActivityLaunchTime, mLastActivityFinishTime);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 978eb1d..7b4fd36 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -35,6 +35,7 @@
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
         "com_android_server_ConsumerIrService.cpp",
+        "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_gpu_GpuService.cpp",
@@ -81,6 +82,7 @@
 
     header_libs: [
         "bionic_libc_platform_headers",
+        "bpf_connectivity_headers",
     ],
 }
 
@@ -161,7 +163,7 @@
         "android.hardware.memtrack-V1-ndk",
         "android.hardware.power@1.0",
         "android.hardware.power@1.1",
-        "android.hardware.power-V2-cpp",
+        "android.hardware.power-V3-cpp",
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
diff --git a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
index ccb4f59..1c574fb 100644
--- a/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
+++ b/services/core/jni/com_android_server_UsbAlsaJackDetector.cpp
@@ -151,13 +151,8 @@
         return -1;
     }
 
-    if (!jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaJackDetector",
-            method_table, NELEM(method_table))) {
-      ALOGE("Can't register UsbAlsaJackDetector native methods");
-      return -1;
-    }
-
-    return 0;
+    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaJackDetector",
+            method_table, NELEM(method_table));
 }
 
 }
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
new file mode 100644
index 0000000..43018a9
--- /dev/null
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -0,0 +1,455 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputController"
+
+#include <android-base/unique_fd.h>
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/uinput.h>
+#include <math.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <utils/Log.h>
+
+#include <map>
+#include <string>
+
+namespace android {
+
+enum class DeviceType {
+    KEYBOARD,
+    MOUSE,
+    TOUCHSCREEN,
+};
+
+enum class UinputAction {
+    RELEASE = 0,
+    PRESS = 1,
+    MOVE = 2,
+    CANCEL = 3,
+};
+
+static std::map<int, UinputAction> BUTTON_ACTION_MAPPING = {
+        {AMOTION_EVENT_ACTION_BUTTON_PRESS, UinputAction::PRESS},
+        {AMOTION_EVENT_ACTION_BUTTON_RELEASE, UinputAction::RELEASE},
+};
+
+static std::map<int, UinputAction> KEY_ACTION_MAPPING = {
+        {AKEY_EVENT_ACTION_DOWN, UinputAction::PRESS},
+        {AKEY_EVENT_ACTION_UP, UinputAction::RELEASE},
+};
+
+static std::map<int, UinputAction> TOUCH_ACTION_MAPPING = {
+        {AMOTION_EVENT_ACTION_DOWN, UinputAction::PRESS},
+        {AMOTION_EVENT_ACTION_UP, UinputAction::RELEASE},
+        {AMOTION_EVENT_ACTION_MOVE, UinputAction::MOVE},
+        {AMOTION_EVENT_ACTION_CANCEL, UinputAction::CANCEL},
+};
+
+// Button code mapping from https://source.android.com/devices/input/touch-devices
+static std::map<int, int> BUTTON_CODE_MAPPING = {
+        {AMOTION_EVENT_BUTTON_PRIMARY, BTN_LEFT},    {AMOTION_EVENT_BUTTON_SECONDARY, BTN_RIGHT},
+        {AMOTION_EVENT_BUTTON_TERTIARY, BTN_MIDDLE}, {AMOTION_EVENT_BUTTON_BACK, BTN_BACK},
+        {AMOTION_EVENT_BUTTON_FORWARD, BTN_FORWARD},
+};
+
+// Tool type mapping from https://source.android.com/devices/input/touch-devices
+static std::map<int, int> TOOL_TYPE_MAPPING = {
+        {AMOTION_EVENT_TOOL_TYPE_FINGER, MT_TOOL_FINGER},
+        {AMOTION_EVENT_TOOL_TYPE_PALM, MT_TOOL_PALM},
+};
+
+// Keycode mapping from https://source.android.com/devices/input/keyboard-devices
+static std::map<int, int> KEY_CODE_MAPPING = {
+        {AKEYCODE_0, KEY_0},
+        {AKEYCODE_1, KEY_1},
+        {AKEYCODE_2, KEY_2},
+        {AKEYCODE_3, KEY_3},
+        {AKEYCODE_4, KEY_4},
+        {AKEYCODE_5, KEY_5},
+        {AKEYCODE_6, KEY_6},
+        {AKEYCODE_7, KEY_7},
+        {AKEYCODE_8, KEY_8},
+        {AKEYCODE_9, KEY_9},
+        {AKEYCODE_A, KEY_A},
+        {AKEYCODE_B, KEY_B},
+        {AKEYCODE_C, KEY_C},
+        {AKEYCODE_D, KEY_D},
+        {AKEYCODE_E, KEY_E},
+        {AKEYCODE_F, KEY_F},
+        {AKEYCODE_G, KEY_G},
+        {AKEYCODE_H, KEY_H},
+        {AKEYCODE_I, KEY_I},
+        {AKEYCODE_J, KEY_J},
+        {AKEYCODE_K, KEY_K},
+        {AKEYCODE_L, KEY_L},
+        {AKEYCODE_M, KEY_M},
+        {AKEYCODE_N, KEY_N},
+        {AKEYCODE_O, KEY_O},
+        {AKEYCODE_P, KEY_P},
+        {AKEYCODE_Q, KEY_Q},
+        {AKEYCODE_R, KEY_R},
+        {AKEYCODE_S, KEY_S},
+        {AKEYCODE_T, KEY_T},
+        {AKEYCODE_U, KEY_U},
+        {AKEYCODE_V, KEY_V},
+        {AKEYCODE_W, KEY_W},
+        {AKEYCODE_X, KEY_X},
+        {AKEYCODE_Y, KEY_Y},
+        {AKEYCODE_Z, KEY_Z},
+        {AKEYCODE_GRAVE, KEY_GRAVE},
+        {AKEYCODE_MINUS, KEY_MINUS},
+        {AKEYCODE_EQUALS, KEY_EQUAL},
+        {AKEYCODE_LEFT_BRACKET, KEY_LEFTBRACE},
+        {AKEYCODE_RIGHT_BRACKET, KEY_RIGHTBRACE},
+        {AKEYCODE_BACKSLASH, KEY_BACKSLASH},
+        {AKEYCODE_SEMICOLON, KEY_SEMICOLON},
+        {AKEYCODE_APOSTROPHE, KEY_APOSTROPHE},
+        {AKEYCODE_COMMA, KEY_COMMA},
+        {AKEYCODE_PERIOD, KEY_DOT},
+        {AKEYCODE_SLASH, KEY_SLASH},
+        {AKEYCODE_ALT_LEFT, KEY_LEFTALT},
+        {AKEYCODE_ALT_RIGHT, KEY_RIGHTALT},
+        {AKEYCODE_CTRL_LEFT, KEY_LEFTCTRL},
+        {AKEYCODE_CTRL_RIGHT, KEY_RIGHTCTRL},
+        {AKEYCODE_SHIFT_LEFT, KEY_LEFTSHIFT},
+        {AKEYCODE_SHIFT_RIGHT, KEY_RIGHTSHIFT},
+        {AKEYCODE_META_LEFT, KEY_LEFTMETA},
+        {AKEYCODE_META_RIGHT, KEY_RIGHTMETA},
+        {AKEYCODE_CAPS_LOCK, KEY_CAPSLOCK},
+        {AKEYCODE_SCROLL_LOCK, KEY_SCROLLLOCK},
+        {AKEYCODE_NUM_LOCK, KEY_NUMLOCK},
+        {AKEYCODE_ENTER, KEY_ENTER},
+        {AKEYCODE_TAB, KEY_TAB},
+        {AKEYCODE_SPACE, KEY_SPACE},
+        {AKEYCODE_DPAD_DOWN, KEY_DOWN},
+        {AKEYCODE_DPAD_UP, KEY_UP},
+        {AKEYCODE_DPAD_LEFT, KEY_LEFT},
+        {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
+        {AKEYCODE_MOVE_END, KEY_END},
+        {AKEYCODE_MOVE_HOME, KEY_HOME},
+        {AKEYCODE_PAGE_DOWN, KEY_PAGEDOWN},
+        {AKEYCODE_PAGE_UP, KEY_PAGEUP},
+        {AKEYCODE_DEL, KEY_BACKSPACE},
+        {AKEYCODE_FORWARD_DEL, KEY_DELETE},
+        {AKEYCODE_INSERT, KEY_INSERT},
+        {AKEYCODE_ESCAPE, KEY_ESC},
+        {AKEYCODE_BREAK, KEY_PAUSE},
+        {AKEYCODE_F1, KEY_F1},
+        {AKEYCODE_F2, KEY_F2},
+        {AKEYCODE_F3, KEY_F3},
+        {AKEYCODE_F4, KEY_F4},
+        {AKEYCODE_F5, KEY_F5},
+        {AKEYCODE_F6, KEY_F6},
+        {AKEYCODE_F7, KEY_F7},
+        {AKEYCODE_F8, KEY_F8},
+        {AKEYCODE_F9, KEY_F9},
+        {AKEYCODE_F10, KEY_F10},
+        {AKEYCODE_F11, KEY_F11},
+        {AKEYCODE_F12, KEY_F12},
+        {AKEYCODE_BACK, KEY_BACK},
+        {AKEYCODE_FORWARD, KEY_FORWARD},
+        {AKEYCODE_NUMPAD_1, KEY_KP1},
+        {AKEYCODE_NUMPAD_2, KEY_KP2},
+        {AKEYCODE_NUMPAD_3, KEY_KP3},
+        {AKEYCODE_NUMPAD_4, KEY_KP4},
+        {AKEYCODE_NUMPAD_5, KEY_KP5},
+        {AKEYCODE_NUMPAD_6, KEY_KP6},
+        {AKEYCODE_NUMPAD_7, KEY_KP7},
+        {AKEYCODE_NUMPAD_8, KEY_KP8},
+        {AKEYCODE_NUMPAD_9, KEY_KP9},
+        {AKEYCODE_NUMPAD_0, KEY_KP0},
+        {AKEYCODE_NUMPAD_ADD, KEY_KPPLUS},
+        {AKEYCODE_NUMPAD_SUBTRACT, KEY_KPMINUS},
+        {AKEYCODE_NUMPAD_MULTIPLY, KEY_KPASTERISK},
+        {AKEYCODE_NUMPAD_DIVIDE, KEY_KPSLASH},
+        {AKEYCODE_NUMPAD_DOT, KEY_KPDOT},
+        {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
+        {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
+        {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+};
+
+/** Creates a new uinput device and assigns a file descriptor. */
+static int openUinput(const char* readableName, jint vendorId, jint productId,
+                      DeviceType deviceType, jint screenHeight, jint screenWidth) {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
+    if (fd < 0) {
+        ALOGE("Error creating uinput device: %s", strerror(errno));
+        return -errno;
+    }
+
+    ioctl(fd, UI_SET_EVBIT, EV_KEY);
+    ioctl(fd, UI_SET_EVBIT, EV_SYN);
+    switch (deviceType) {
+        case DeviceType::KEYBOARD:
+            for (const auto& [ignored, keyCode] : KEY_CODE_MAPPING) {
+                ioctl(fd, UI_SET_KEYBIT, keyCode);
+            }
+            break;
+        case DeviceType::MOUSE:
+            ioctl(fd, UI_SET_EVBIT, EV_REL);
+            ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
+            ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);
+            ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE);
+            ioctl(fd, UI_SET_KEYBIT, BTN_BACK);
+            ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD);
+            ioctl(fd, UI_SET_RELBIT, REL_X);
+            ioctl(fd, UI_SET_RELBIT, REL_Y);
+            ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
+            ioctl(fd, UI_SET_RELBIT, REL_HWHEEL);
+            break;
+        case DeviceType::TOUCHSCREEN:
+            ioctl(fd, UI_SET_EVBIT, EV_ABS);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
+            ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+    }
+
+    int version;
+    if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) {
+        uinput_setup setup;
+        memset(&setup, 0, sizeof(setup));
+        strlcpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE);
+        setup.id.version = 1;
+        setup.id.bustype = BUS_VIRTUAL;
+        setup.id.vendor = vendorId;
+        setup.id.product = productId;
+        if (deviceType == DeviceType::TOUCHSCREEN) {
+            uinput_abs_setup xAbsSetup;
+            xAbsSetup.code = ABS_MT_POSITION_X;
+            xAbsSetup.absinfo.maximum = screenWidth - 1;
+            xAbsSetup.absinfo.minimum = 0;
+            ioctl(fd, UI_ABS_SETUP, xAbsSetup);
+            uinput_abs_setup yAbsSetup;
+            yAbsSetup.code = ABS_MT_POSITION_Y;
+            yAbsSetup.absinfo.maximum = screenHeight - 1;
+            yAbsSetup.absinfo.minimum = 0;
+            ioctl(fd, UI_ABS_SETUP, yAbsSetup);
+            uinput_abs_setup majorAbsSetup;
+            majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
+            majorAbsSetup.absinfo.maximum = screenWidth - 1;
+            majorAbsSetup.absinfo.minimum = 0;
+            ioctl(fd, UI_ABS_SETUP, majorAbsSetup);
+            uinput_abs_setup pressureAbsSetup;
+            pressureAbsSetup.code = ABS_MT_PRESSURE;
+            pressureAbsSetup.absinfo.maximum = 255;
+            pressureAbsSetup.absinfo.minimum = 0;
+            ioctl(fd, UI_ABS_SETUP, pressureAbsSetup);
+        }
+        if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
+            ALOGE("Error creating uinput device: %s", strerror(errno));
+            return -errno;
+        }
+    } else {
+        // UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
+        uinput_user_dev fallback;
+        memset(&fallback, 0, sizeof(fallback));
+        strlcpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE);
+        fallback.id.version = 1;
+        fallback.id.bustype = BUS_VIRTUAL;
+        fallback.id.vendor = vendorId;
+        fallback.id.product = productId;
+        if (deviceType == DeviceType::TOUCHSCREEN) {
+            fallback.absmin[ABS_MT_POSITION_X] = 0;
+            fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1;
+            fallback.absmin[ABS_MT_POSITION_Y] = 0;
+            fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1;
+            fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0;
+            fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1;
+            fallback.absmin[ABS_MT_PRESSURE] = 0;
+            fallback.absmax[ABS_MT_PRESSURE] = 255;
+        }
+        if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
+            ALOGE("Error creating uinput device: %s", strerror(errno));
+            return -errno;
+        }
+    }
+
+    if (ioctl(fd, UI_DEV_CREATE) != 0) {
+        ALOGE("Error creating uinput device: %s", strerror(errno));
+        return -errno;
+    }
+
+    return fd.release();
+}
+
+static int openUinputJni(JNIEnv* env, jstring name, jint vendorId, jint productId,
+                         DeviceType deviceType, int screenHeight, int screenWidth) {
+    ScopedUtfChars readableName(env, name);
+    return openUinput(readableName.c_str(), vendorId, productId, deviceType, screenHeight,
+                      screenWidth);
+}
+
+static int nativeOpenUinputKeyboard(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                    jint productId) {
+    return openUinputJni(env, name, vendorId, productId, DeviceType::KEYBOARD, /* screenHeight */ 0,
+                         /* screenWidth */ 0);
+}
+
+static int nativeOpenUinputMouse(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                 jint productId) {
+    return openUinputJni(env, name, vendorId, productId, DeviceType::MOUSE, /* screenHeight */ 0,
+                         /* screenWidth */ 0);
+}
+
+static int nativeOpenUinputTouchscreen(JNIEnv* env, jobject thiz, jstring name, jint vendorId,
+                                       jint productId, jint height, jint width) {
+    return openUinputJni(env, name, vendorId, productId, DeviceType::TOUCHSCREEN, height, width);
+}
+
+static bool nativeCloseUinput(JNIEnv* env, jobject thiz, jint fd) {
+    ioctl(fd, UI_DEV_DESTROY);
+    return close(fd);
+}
+
+static bool writeInputEvent(int fd, uint16_t type, uint16_t code, int32_t value) {
+    struct input_event ev = {.type = type, .code = code, .value = value};
+    return TEMP_FAILURE_RETRY(write(fd, &ev, sizeof(struct input_event))) == sizeof(ev);
+}
+
+static bool nativeWriteKeyEvent(JNIEnv* env, jobject thiz, jint fd, jint androidKeyCode,
+                                jint action) {
+    auto keyCodeIterator = KEY_CODE_MAPPING.find(androidKeyCode);
+    if (keyCodeIterator == KEY_CODE_MAPPING.end()) {
+        ALOGE("No supportive native keycode for androidKeyCode %d", androidKeyCode);
+        return false;
+    }
+    auto actionIterator = KEY_ACTION_MAPPING.find(action);
+    if (actionIterator == KEY_ACTION_MAPPING.end()) {
+        return false;
+    }
+    if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(keyCodeIterator->second),
+                         static_cast<int32_t>(actionIterator->second))) {
+        return false;
+    }
+    if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) {
+        return false;
+    }
+    return true;
+}
+
+static bool nativeWriteButtonEvent(JNIEnv* env, jobject thiz, jint fd, jint buttonCode,
+                                   jint action) {
+    auto buttonCodeIterator = BUTTON_CODE_MAPPING.find(buttonCode);
+    if (buttonCodeIterator == BUTTON_CODE_MAPPING.end()) {
+        return false;
+    }
+    auto actionIterator = BUTTON_ACTION_MAPPING.find(action);
+    if (actionIterator == BUTTON_ACTION_MAPPING.end()) {
+        return false;
+    }
+    if (!writeInputEvent(fd, EV_KEY, static_cast<uint16_t>(buttonCodeIterator->second),
+                         static_cast<int32_t>(actionIterator->second))) {
+        return false;
+    }
+    if (!writeInputEvent(fd, EV_SYN, SYN_REPORT, 0)) {
+        return false;
+    }
+    return true;
+}
+
+static bool nativeWriteTouchEvent(JNIEnv* env, jobject thiz, jint fd, jint pointerId, jint toolType,
+                                  jint action, jfloat locationX, jfloat locationY, jfloat pressure,
+                                  jfloat majorAxisSize) {
+    if (!writeInputEvent(fd, EV_ABS, ABS_MT_SLOT, pointerId)) {
+        return false;
+    }
+    auto toolTypeIterator = TOOL_TYPE_MAPPING.find(toolType);
+    if (toolTypeIterator == TOOL_TYPE_MAPPING.end()) {
+        return false;
+    }
+    if (toolType != -1) {
+        if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOOL_TYPE,
+                             static_cast<int32_t>(toolTypeIterator->second))) {
+            return false;
+        }
+    }
+    auto actionIterator = TOUCH_ACTION_MAPPING.find(action);
+    if (actionIterator == TOUCH_ACTION_MAPPING.end()) {
+        return false;
+    }
+    UinputAction uinputAction = actionIterator->second;
+    if (uinputAction == UinputAction::PRESS || uinputAction == UinputAction::RELEASE) {
+        if (!writeInputEvent(fd, EV_KEY, BTN_TOUCH, static_cast<int32_t>(uinputAction))) {
+            return false;
+        }
+        if (!writeInputEvent(fd, EV_ABS, ABS_MT_TRACKING_ID,
+                             static_cast<int32_t>(uinputAction == UinputAction::PRESS ? pointerId
+                                                                                      : -1))) {
+            return false;
+        }
+    }
+    if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_X, locationX)) {
+        return false;
+    }
+    if (!writeInputEvent(fd, EV_ABS, ABS_MT_POSITION_Y, locationY)) {
+        return false;
+    }
+    if (!isnan(pressure)) {
+        if (!writeInputEvent(fd, EV_ABS, ABS_MT_PRESSURE, pressure)) {
+            return false;
+        }
+    }
+    if (!isnan(majorAxisSize)) {
+        if (!writeInputEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, majorAxisSize)) {
+            return false;
+        }
+    }
+    return writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static bool nativeWriteRelativeEvent(JNIEnv* env, jobject thiz, jint fd, jfloat relativeX,
+                                     jfloat relativeY) {
+    return writeInputEvent(fd, EV_REL, REL_X, relativeX) &&
+            writeInputEvent(fd, EV_REL, REL_Y, relativeY) &&
+            writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static bool nativeWriteScrollEvent(JNIEnv* env, jobject thiz, jint fd, jfloat xAxisMovement,
+                                   jfloat yAxisMovement) {
+    return writeInputEvent(fd, EV_REL, REL_HWHEEL, xAxisMovement) &&
+            writeInputEvent(fd, EV_REL, REL_WHEEL, yAxisMovement) &&
+            writeInputEvent(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static JNINativeMethod methods[] = {
+        {"nativeOpenUinputKeyboard", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputKeyboard},
+        {"nativeOpenUinputMouse", "(Ljava/lang/String;II)I", (void*)nativeOpenUinputMouse},
+        {"nativeOpenUinputTouchscreen", "(Ljava/lang/String;IIII)I",
+         (void*)nativeOpenUinputTouchscreen},
+        {"nativeCloseUinput", "(I)Z", (void*)nativeCloseUinput},
+        {"nativeWriteKeyEvent", "(III)Z", (void*)nativeWriteKeyEvent},
+        {"nativeWriteButtonEvent", "(III)Z", (void*)nativeWriteButtonEvent},
+        {"nativeWriteTouchEvent", "(IIIIFFFF)Z", (void*)nativeWriteTouchEvent},
+        {"nativeWriteRelativeEvent", "(IFF)Z", (void*)nativeWriteRelativeEvent},
+        {"nativeWriteScrollEvent", "(IFF)Z", (void*)nativeWriteScrollEvent},
+};
+
+int register_android_server_companion_virtual_InputController(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/companion/virtual/InputController",
+                                    methods, NELEM(methods));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 4504853..f0f779d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -37,10 +37,7 @@
 #include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
 #include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
 #include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h>
-#include <arpa/inet.h>
 #include <binder/IServiceManager.h>
-#include <linux/in.h>
-#include <linux/in6.h>
 #include <nativehelper/JNIHelp.h>
 #include <pthread.h>
 #include <string.h>
@@ -52,6 +49,7 @@
 
 #include "android_runtime/AndroidRuntime.h"
 #include "android_runtime/Log.h"
+#include "gnss/AGnss.h"
 #include "gnss/GnssAntennaInfoCallback.h"
 #include "gnss/GnssBatching.h"
 #include "gnss/GnssConfiguration.h"
@@ -69,7 +67,6 @@
 static jmethodID method_reportLocation;
 static jmethodID method_reportStatus;
 static jmethodID method_reportSvStatus;
-static jmethodID method_reportAGpsStatus;
 static jmethodID method_reportNmea;
 static jmethodID method_setTopHalCapabilities;
 static jmethodID method_setGnssYearOfHardware;
@@ -163,10 +160,6 @@
 using IGnssAntennaInfo = android::hardware::gnss::V2_1::IGnssAntennaInfo;
 using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil;
 using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil;
-using IAGnss_V1_0 = android::hardware::gnss::V1_0::IAGnss;
-using IAGnss_V2_0 = android::hardware::gnss::V2_0::IAGnss;
-using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
-using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
 
 using IMeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
 using IMeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections;
@@ -182,6 +175,7 @@
 using android::hardware::gnss::IGnssPowerIndication;
 using android::hardware::gnss::IGnssPowerIndicationCallback;
 using android::hardware::gnss::PsdsType;
+using IAGnssAidl = android::hardware::gnss::IAGnss;
 using IGnssAidl = android::hardware::gnss::IGnss;
 using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
 using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
@@ -215,8 +209,6 @@
 sp<IGnssXtra> gnssXtraIface = nullptr;
 sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
 sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
-sp<IAGnss_V1_0> agnssIface = nullptr;
-sp<IAGnss_V2_0> agnssIface_V2_0 = nullptr;
 sp<IGnssDebug_V1_0> gnssDebugIface = nullptr;
 sp<IGnssDebug_V2_0> gnssDebugIface_V2_0 = nullptr;
 sp<IGnssNi> gnssNiIface = nullptr;
@@ -231,6 +223,7 @@
 std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr;
 std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr;
 std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr;
+std::unique_ptr<android::gnss::AGnssInterface> agnssIface = nullptr;
 
 #define WAKE_LOCK_NAME  "GPS"
 
@@ -255,34 +248,6 @@
     return value ? JNI_TRUE : JNI_FALSE;
 }
 
-struct ScopedJniString {
-    ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
-        mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
-    }
-
-    ~ScopedJniString() {
-        if (mNativeString != nullptr) {
-            mEnv->ReleaseStringUTFChars(mJavaString, mNativeString);
-        }
-    }
-
-    const char* c_str() const {
-        return mNativeString;
-    }
-
-    operator hidl_string() const {
-        return hidl_string(mNativeString);
-    }
-
-private:
-    ScopedJniString(const ScopedJniString&) = delete;
-    ScopedJniString& operator=(const ScopedJniString&) = delete;
-
-    JNIEnv* mEnv;
-    jstring mJavaString;
-    const char* mNativeString;
-};
-
 static GnssLocation_V1_0 createGnssLocation_V1_0(
         jint gnssLocationFlags, jdouble latitudeDegrees, jdouble longitudeDegrees,
         jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
@@ -806,122 +771,6 @@
 }
 
 /*
- * AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface.
- */
-struct AGnssCallback_V1_0 : public IAGnssCallback_V1_0 {
-    // Methods from ::android::hardware::gps::V1_0::IAGnssCallback follow.
-    Return<void> agnssStatusIpV6Cb(
-      const IAGnssCallback_V1_0::AGnssStatusIpV6& agps_status) override;
-
-    Return<void> agnssStatusIpV4Cb(
-      const IAGnssCallback_V1_0::AGnssStatusIpV4& agps_status) override;
- private:
-    jbyteArray convertToIpV4(uint32_t ip);
-};
-
-Return<void> AGnssCallback_V1_0::agnssStatusIpV6Cb(
-        const IAGnssCallback_V1_0::AGnssStatusIpV6& agps_status) {
-    JNIEnv* env = getJniEnv();
-    jbyteArray byteArray = nullptr;
-
-    byteArray = env->NewByteArray(16);
-    if (byteArray != nullptr) {
-        env->SetByteArrayRegion(byteArray, 0, 16,
-                                (const jbyte*)(agps_status.ipV6Addr.data()));
-    } else {
-        ALOGE("Unable to allocate byte array for IPv6 address.");
-    }
-
-    IF_ALOGD() {
-        // log the IP for reference in case there is a bogus value pushed by HAL
-        char str[INET6_ADDRSTRLEN];
-        inet_ntop(AF_INET6, agps_status.ipV6Addr.data(), str, INET6_ADDRSTRLEN);
-        ALOGD("AGPS IP is v6: %s", str);
-    }
-
-    jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
-    ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
-    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus,
-                        agps_status.type, agps_status.status, byteArray);
-
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-
-    if (byteArray) {
-        env->DeleteLocalRef(byteArray);
-    }
-
-    return Void();
-}
-
-Return<void> AGnssCallback_V1_0::agnssStatusIpV4Cb(
-        const IAGnssCallback_V1_0::AGnssStatusIpV4& agps_status) {
-    JNIEnv* env = getJniEnv();
-    jbyteArray byteArray = nullptr;
-
-    uint32_t ipAddr = agps_status.ipV4Addr;
-    byteArray = convertToIpV4(ipAddr);
-
-    IF_ALOGD() {
-        /*
-         * log the IP for reference in case there is a bogus value pushed by
-         * HAL.
-         */
-        char str[INET_ADDRSTRLEN];
-        inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN);
-        ALOGD("AGPS IP is v4: %s", str);
-    }
-
-    jsize byteArrayLength =
-      byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
-    ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
-    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus,
-                      agps_status.type, agps_status.status, byteArray);
-
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-
-    if (byteArray) {
-        env->DeleteLocalRef(byteArray);
-    }
-    return Void();
-}
-
-jbyteArray AGnssCallback_V1_0::convertToIpV4(uint32_t ip) {
-    if (INADDR_NONE == ip) {
-        return nullptr;
-    }
-
-    JNIEnv* env = getJniEnv();
-    jbyteArray byteArray = env->NewByteArray(4);
-    if (byteArray == nullptr) {
-        ALOGE("Unable to allocate byte array for IPv4 address");
-        return nullptr;
-    }
-
-    jbyte ipv4[4];
-    ALOGV("Converting IPv4 address byte array (net_order) %x", ip);
-    memcpy(ipv4, &ip, sizeof(ipv4));
-    env->SetByteArrayRegion(byteArray, 0, 4, (const jbyte*)ipv4);
-    return byteArray;
-}
-
-/*
- * AGnssCallback_V2_0 implements callback methods required by the IAGnssCallback 2.0 interface.
- */
-struct AGnssCallback_V2_0 : public IAGnssCallback_V2_0 {
-    // Methods from ::android::hardware::gps::V2_0::IAGnssCallback follow.
-    Return<void> agnssStatusCb(IAGnssCallback_V2_0::AGnssType type,
-        IAGnssCallback_V2_0::AGnssStatusValue status) override;
-};
-
-Return<void> AGnssCallback_V2_0::agnssStatusCb(IAGnssCallback_V2_0::AGnssType type,
-        IAGnssCallback_V2_0::AGnssStatusValue status) {
-    JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, type, status, nullptr);
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
-/*
  * AGnssRilCallback implements the callback methods required by the AGnssRil
  * interface.
  */
@@ -990,7 +839,6 @@
             "(ZLandroid/location/Location;)V");
     method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
     method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F[F)V");
-    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
     method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
@@ -1075,8 +923,10 @@
     gnss::GnssAntennaInfo_class_init_once(env, clazz);
     gnss::GnssBatching_class_init_once(env, clazz);
     gnss::GnssConfiguration_class_init_once(env);
+    gnss::GnssGeofence_class_init_once(env, clazz);
     gnss::GnssMeasurement_class_init_once(env, clazz);
     gnss::GnssNavigationMessage_class_init_once(env, clazz);
+    gnss::AGnss_class_init_once(env, clazz);
     gnss::Utils_class_init_once(env);
 }
 
@@ -1147,19 +997,21 @@
         }
     }
 
-    if (gnssHal_V2_0 != nullptr) {
+    if (gnssHalAidl != nullptr && gnssHalAidl->getInterfaceVersion() >= 2) {
+        sp<IAGnssAidl> agnssAidl;
+        auto status = gnssHalAidl->getExtensionAGnss(&agnssAidl);
+        if (checkAidlStatus(status, "Unable to get a handle to AGnss interface.")) {
+            agnssIface = std::make_unique<gnss::AGnss>(agnssAidl);
+        }
+    } else if (gnssHal_V2_0 != nullptr) {
         auto agnss_V2_0 = gnssHal_V2_0->getExtensionAGnss_2_0();
-        if (!agnss_V2_0.isOk()) {
-            ALOGD("Unable to get a handle to AGnss_V2_0");
-        } else {
-            agnssIface_V2_0 = agnss_V2_0;
+        if (checkHidlReturn(agnss_V2_0, "Unable to get a handle to AGnss_V2_0")) {
+            agnssIface = std::make_unique<gnss::AGnss_V2_0>(agnss_V2_0);
         }
     } else if (gnssHal != nullptr) {
         auto agnss_V1_0 = gnssHal->getExtensionAGnss();
-        if (!agnss_V1_0.isOk()) {
-            ALOGD("Unable to get a handle to AGnss");
-        } else {
-            agnssIface = agnss_V1_0;
+        if (checkHidlReturn(agnss_V1_0, "Unable to get a handle to AGnss_V1_0")) {
+            agnssIface = std::make_unique<gnss::AGnss_V1_0>(agnss_V1_0);
         }
     }
 
@@ -1452,16 +1304,9 @@
         }
     }
 
-    // Set IAGnss.hal callback.
-    if (agnssIface_V2_0 != nullptr) {
-        sp<IAGnssCallback_V2_0> aGnssCbIface = new AGnssCallback_V2_0();
-        auto agnssStatus = agnssIface_V2_0->setCallback(aGnssCbIface);
-        checkHidlReturn(agnssStatus, "IAGnss 2.0 setCallback() failed.");
-    } else if (agnssIface != nullptr) {
-        sp<IAGnssCallback_V1_0> aGnssCbIface = new AGnssCallback_V1_0();
-        auto agnssStatus = agnssIface->setCallback(aGnssCbIface);
-        checkHidlReturn(agnssStatus, "IAGnss setCallback() failed.");
-    } else {
+    // Set IAGnss callback.
+    if (agnssIface == nullptr ||
+        !agnssIface->setCallback(std::make_unique<gnss::AGnssCallback>())) {
         ALOGI("Unable to initialize IAGnss interface.");
     }
 
@@ -1737,61 +1582,6 @@
     env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT);
 }
 
-struct AGnssDispatcher {
-    static void dataConnOpen(sp<IAGnss_V1_0> agnssIface, JNIEnv* env, jstring apn, jint apnIpType);
-    static void dataConnOpen(sp<IAGnss_V2_0> agnssIface_V2_0, JNIEnv* env, jlong networkHandle,
-            jstring apn, jint apnIpType);
-
-    template <class T>
-    static void dataConnClosed(sp<T> agnssIface);
-
-    template <class T>
-    static void dataConnFailed(sp<T> agnssIface);
-
-    template <class T, class U>
-    static void setServer(sp<T> agnssIface, JNIEnv* env, jint type, jstring hostname, jint port);
-
-private:
-    AGnssDispatcher() = delete;
-};
-
-void AGnssDispatcher::dataConnOpen(sp<IAGnss_V1_0> agnssIface, JNIEnv* env, jstring apn,
-        jint apnIpType) {
-    ScopedJniString jniApn{env, apn};
-    auto result = agnssIface->dataConnOpen(jniApn,
-            static_cast<IAGnss_V1_0::ApnIpType>(apnIpType));
-    checkHidlReturn(result, "IAGnss dataConnOpen() failed. APN and its IP type not set.");
-}
-
-void AGnssDispatcher::dataConnOpen(sp<IAGnss_V2_0> agnssIface_V2_0, JNIEnv* env,
-        jlong networkHandle, jstring apn, jint apnIpType) {
-    ScopedJniString jniApn{env, apn};
-    auto result = agnssIface_V2_0->dataConnOpen(static_cast<uint64_t>(networkHandle), jniApn,
-            static_cast<IAGnss_V2_0::ApnIpType>(apnIpType));
-    checkHidlReturn(result, "IAGnss 2.0 dataConnOpen() failed. APN and its IP type not set.");
-}
-
-template<class T>
-void AGnssDispatcher::dataConnClosed(sp<T> agnssIface) {
-    auto result = agnssIface->dataConnClosed();
-    checkHidlReturn(result, "IAGnss dataConnClosed() failed.");
-}
-
-template<class T>
-void AGnssDispatcher::dataConnFailed(sp<T> agnssIface) {
-    auto result = agnssIface->dataConnFailed();
-    checkHidlReturn(result, "IAGnss dataConnFailed() failed.");
-}
-
-template <class T, class U>
-void AGnssDispatcher::setServer(sp<T> agnssIface, JNIEnv* env, jint type, jstring hostname,
-        jint port) {
-    ScopedJniString jniHostName{env, hostname};
-    auto result = agnssIface->setServer(static_cast<typename U::AGnssType>(type),
-            jniHostName, port);
-    checkHidlReturn(result, "IAGnss setServer() failed. Host name and port not set.");
-}
-
 static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_open(
         JNIEnv* env, jobject /* obj */, jlong networkHandle, jstring apn, jint apnIpType) {
     if (apn == nullptr) {
@@ -1799,10 +1589,8 @@
         return;
     }
 
-    if (agnssIface_V2_0 != nullptr) {
-        AGnssDispatcher::dataConnOpen(agnssIface_V2_0, env, networkHandle, apn, apnIpType);
-    } else if (agnssIface != nullptr) {
-        AGnssDispatcher::dataConnOpen(agnssIface, env, apn, apnIpType);
+    if (agnssIface != nullptr) {
+        agnssIface->dataConnOpen(env, networkHandle, apn, apnIpType);
     } else {
         ALOGE("%s: IAGnss interface not available.", __func__);
         return;
@@ -1811,10 +1599,8 @@
 
 static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_closed(JNIEnv* /* env */,
                                                                        jobject /* obj */) {
-    if (agnssIface_V2_0 != nullptr) {
-        AGnssDispatcher::dataConnClosed(agnssIface_V2_0);
-    } else if (agnssIface != nullptr) {
-        AGnssDispatcher::dataConnClosed(agnssIface);
+    if (agnssIface != nullptr) {
+        agnssIface->dataConnClosed();
     } else {
         ALOGE("%s: IAGnss interface not available.", __func__);
         return;
@@ -1823,10 +1609,8 @@
 
 static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_failed(JNIEnv* /* env */,
                                                                        jobject /* obj */) {
-    if (agnssIface_V2_0 != nullptr) {
-        AGnssDispatcher::dataConnFailed(agnssIface_V2_0);
-    } else if (agnssIface != nullptr) {
-        AGnssDispatcher::dataConnFailed(agnssIface);
+    if (agnssIface != nullptr) {
+        agnssIface->dataConnFailed();
     } else {
         ALOGE("%s: IAGnss interface not available.", __func__);
         return;
@@ -1835,12 +1619,8 @@
 
 static void android_location_gnss_hal_GnssNative_set_agps_server(JNIEnv* env, jclass, jint type,
                                                                  jstring hostname, jint port) {
-    if (agnssIface_V2_0 != nullptr) {
-        AGnssDispatcher::setServer<IAGnss_V2_0, IAGnssCallback_V2_0>(agnssIface_V2_0, env, type,
-                hostname, port);
-    } else if (agnssIface != nullptr) {
-        AGnssDispatcher::setServer<IAGnss_V1_0, IAGnssCallback_V1_0>(agnssIface, env, type,
-                hostname, port);
+    if (agnssIface != nullptr) {
+        agnssIface->setServer(env, type, hostname, port);
     } else {
         ALOGE("%s: IAGnss interface not available.", __func__);
         return;
diff --git a/services/core/jni/gnss/AGnss.cpp b/services/core/jni/gnss/AGnss.cpp
new file mode 100644
index 0000000..00403d6
--- /dev/null
+++ b/services/core/jni/gnss/AGnss.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "AGnssJni"
+
+#include "AGnss.h"
+
+#include "Utils.h"
+
+using android::hardware::gnss::IAGnss;
+using IAGnss_V1_0 = android::hardware::gnss::V1_0::IAGnss;
+using IAGnss_V2_0 = android::hardware::gnss::V2_0::IAGnss;
+using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
+using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
+using AGnssType = android::hardware::gnss::IAGnssCallback::AGnssType;
+
+namespace android::gnss {
+
+// Implementation of AGnss (AIDL HAL)
+
+AGnss::AGnss(const sp<IAGnss>& iAGnss) : mIAGnss(iAGnss) {
+    assert(mIAGnss != nullptr);
+}
+
+jboolean AGnss::setCallback(const std::unique_ptr<AGnssCallback>& callback) {
+    auto status = mIAGnss->setCallback(callback->getAidl());
+    return checkAidlStatus(status, "IAGnssAidl setCallback() failed.");
+}
+
+jboolean AGnss::dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) {
+    ScopedJniString jniApn{env, apn};
+    auto status = mIAGnss->dataConnOpen(networkHandle, String16(jniApn.c_str()),
+                                        static_cast<IAGnss::ApnIpType>(apnIpType));
+    return checkAidlStatus(status,
+                           "IAGnssAidl dataConnOpen() failed. APN and its IP type not set.");
+}
+
+jboolean AGnss::dataConnClosed() {
+    auto status = mIAGnss->dataConnClosed();
+    return checkAidlStatus(status, "IAGnssAidl dataConnClosed() failed.");
+}
+
+jboolean AGnss::dataConnFailed() {
+    auto status = mIAGnss->dataConnFailed();
+    return checkAidlStatus(status, "IAGnssAidl dataConnFailed() failed.");
+}
+
+jboolean AGnss::setServer(JNIEnv* env, jint type, jstring hostname, jint port) {
+    ScopedJniString jniHostName{env, hostname};
+    auto status =
+            mIAGnss->setServer(static_cast<AGnssType>(type), String16(jniHostName.c_str()), port);
+    return checkAidlStatus(status, "IAGnssAidl setServer() failed. Host name and port not set.");
+}
+
+// Implementation of AGnss_V1_0
+
+AGnss_V1_0::AGnss_V1_0(const sp<IAGnss_V1_0>& iAGnss) : mIAGnss_V1_0(iAGnss) {
+    assert(mIAGnss_V1_0 != nullptr);
+}
+
+jboolean AGnss_V1_0::setCallback(const std::unique_ptr<AGnssCallback>& callback) {
+    auto result = mIAGnss_V1_0->setCallback(callback->getV1_0());
+    return checkHidlReturn(result, "IAGnss_V1_0 setCallback() failed.");
+}
+
+jboolean AGnss_V1_0::dataConnOpen(JNIEnv* env, jlong, jstring apn, jint apnIpType) {
+    ScopedJniString jniApn{env, apn};
+    auto result =
+            mIAGnss_V1_0->dataConnOpen(jniApn, static_cast<IAGnss_V1_0::ApnIpType>(apnIpType));
+    return checkHidlReturn(result,
+                           "IAGnss_V1_0 dataConnOpen() failed. APN and its IP type not set.");
+}
+
+jboolean AGnss_V1_0::dataConnClosed() {
+    auto result = mIAGnss_V1_0->dataConnClosed();
+    return checkHidlReturn(result, "IAGnss_V1_0 dataConnClosed() failed.");
+}
+
+jboolean AGnss_V1_0::dataConnFailed() {
+    auto result = mIAGnss_V1_0->dataConnFailed();
+    return checkHidlReturn(result, "IAGnss_V1_0 dataConnFailed() failed.");
+}
+
+jboolean AGnss_V1_0::setServer(JNIEnv* env, jint type, jstring hostname, jint port) {
+    ScopedJniString jniHostName{env, hostname};
+    auto result = mIAGnss_V1_0->setServer(static_cast<IAGnssCallback_V1_0::AGnssType>(type),
+                                          jniHostName, port);
+    return checkHidlReturn(result, "IAGnss_V1_0 setServer() failed. Host name and port not set.");
+}
+
+// Implementation of AGnss_V2_0
+
+AGnss_V2_0::AGnss_V2_0(const sp<IAGnss_V2_0>& iAGnss) : mIAGnss_V2_0(iAGnss) {
+    assert(mIAGnss_V2_0 != nullptr);
+}
+
+jboolean AGnss_V2_0::setCallback(const std::unique_ptr<AGnssCallback>& callback) {
+    auto result = mIAGnss_V2_0->setCallback(callback->getV2_0());
+    return checkHidlReturn(result, "IAGnss_V2_0 setCallback() failed.");
+}
+
+jboolean AGnss_V2_0::dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) {
+    ScopedJniString jniApn{env, apn};
+    auto result = mIAGnss_V2_0->dataConnOpen(static_cast<uint64_t>(networkHandle), jniApn,
+                                             static_cast<IAGnss_V2_0::ApnIpType>(apnIpType));
+    return checkHidlReturn(result,
+                           "IAGnss_V2_0 dataConnOpen() failed. APN and its IP type not set.");
+}
+
+jboolean AGnss_V2_0::dataConnClosed() {
+    auto result = mIAGnss_V2_0->dataConnClosed();
+    return checkHidlReturn(result, "IAGnss_V2_0 dataConnClosed() failed.");
+}
+
+jboolean AGnss_V2_0::dataConnFailed() {
+    auto result = mIAGnss_V2_0->dataConnFailed();
+    return checkHidlReturn(result, "IAGnss_V2_0 dataConnFailed() failed.");
+}
+
+jboolean AGnss_V2_0::setServer(JNIEnv* env, jint type, jstring hostname, jint port) {
+    ScopedJniString jniHostName{env, hostname};
+    auto result = mIAGnss_V2_0->setServer(static_cast<IAGnssCallback_V2_0::AGnssType>(type),
+                                          jniHostName, port);
+    return checkHidlReturn(result, "IAGnss_V2_0 setServer() failed. Host name and port not set.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/AGnss.h b/services/core/jni/gnss/AGnss.h
new file mode 100644
index 0000000..2828b82
--- /dev/null
+++ b/services/core/jni/gnss/AGnss.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_AGNSS_H
+#define _ANDROID_SERVER_GNSS_AGNSS_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/BnAGnss.h>
+#include <log/log.h>
+
+#include "AGnssCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class AGnssInterface {
+public:
+    virtual ~AGnssInterface() {}
+    virtual jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) = 0;
+    virtual jboolean dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn,
+                                  jint apnIpType) = 0;
+    virtual jboolean dataConnClosed() = 0;
+    virtual jboolean dataConnFailed() = 0;
+    virtual jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) = 0;
+};
+
+class AGnss : public AGnssInterface {
+public:
+    AGnss(const sp<android::hardware::gnss::IAGnss>& iAGnss);
+    jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) override;
+    jboolean dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) override;
+    jboolean dataConnClosed() override;
+    jboolean dataConnFailed() override;
+    jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) override;
+
+private:
+    const sp<android::hardware::gnss::IAGnss> mIAGnss;
+};
+
+class AGnss_V1_0 : public AGnssInterface {
+public:
+    AGnss_V1_0(const sp<android::hardware::gnss::V1_0::IAGnss>& iAGnss);
+    jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) override;
+    jboolean dataConnOpen(JNIEnv* env, jlong, jstring apn, jint apnIpType) override;
+    jboolean dataConnClosed() override;
+    jboolean dataConnFailed() override;
+    jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) override;
+
+private:
+    const sp<android::hardware::gnss::V1_0::IAGnss> mIAGnss_V1_0;
+};
+
+class AGnss_V2_0 : public AGnssInterface {
+public:
+    AGnss_V2_0(const sp<android::hardware::gnss::V2_0::IAGnss>& iAGnss);
+    jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) override;
+    jboolean dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) override;
+    jboolean dataConnClosed() override;
+    jboolean dataConnFailed() override;
+    jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) override;
+
+private:
+    const sp<android::hardware::gnss::V2_0::IAGnss> mIAGnss_V2_0;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_AGNSS_H
diff --git a/services/core/jni/gnss/AGnssCallback.cpp b/services/core/jni/gnss/AGnssCallback.cpp
new file mode 100644
index 0000000..466cdfa
--- /dev/null
+++ b/services/core/jni/gnss/AGnssCallback.cpp
@@ -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.
+ */
+
+#define LOG_TAG "AGnssCbJni"
+
+#include "AGnssCallback.h"
+
+namespace android::gnss {
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
+using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
+
+namespace {
+
+jmethodID method_reportAGpsStatus;
+
+}
+
+void AGnss_class_init_once(JNIEnv* env, jclass clazz) {
+    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
+}
+
+Status AGnssCallbackAidl::agnssStatusCb(AGnssType type, AGnssStatusValue status) {
+    AGnssCallbackUtil::agnssStatusCbImpl(type, status);
+    return Status::ok();
+}
+
+Return<void> AGnssCallback_V1_0::agnssStatusIpV6Cb(
+        const IAGnssCallback_V1_0::AGnssStatusIpV6& agps_status) {
+    JNIEnv* env = getJniEnv();
+    jbyteArray byteArray = nullptr;
+
+    byteArray = env->NewByteArray(16);
+    if (byteArray != nullptr) {
+        env->SetByteArrayRegion(byteArray, 0, 16, (const jbyte*)(agps_status.ipV6Addr.data()));
+    } else {
+        ALOGE("Unable to allocate byte array for IPv6 address.");
+    }
+
+    IF_ALOGD() {
+        // log the IP for reference in case there is a bogus value pushed by HAL
+        char str[INET6_ADDRSTRLEN];
+        inet_ntop(AF_INET6, agps_status.ipV6Addr.data(), str, INET6_ADDRSTRLEN);
+        ALOGD("AGPS IP is v6: %s", str);
+    }
+
+    jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
+    ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
+    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status.type,
+                        agps_status.status, byteArray);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+
+    if (byteArray) {
+        env->DeleteLocalRef(byteArray);
+    }
+
+    return Void();
+}
+
+Return<void> AGnssCallback_V1_0::agnssStatusIpV4Cb(
+        const IAGnssCallback_V1_0::AGnssStatusIpV4& agps_status) {
+    JNIEnv* env = getJniEnv();
+    jbyteArray byteArray = nullptr;
+
+    uint32_t ipAddr = agps_status.ipV4Addr;
+    byteArray = convertToIpV4(ipAddr);
+
+    IF_ALOGD() {
+        /*
+         * log the IP for reference in case there is a bogus value pushed by
+         * HAL.
+         */
+        char str[INET_ADDRSTRLEN];
+        inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN);
+        ALOGD("AGPS IP is v4: %s", str);
+    }
+
+    jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
+    ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
+    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status.type,
+                        agps_status.status, byteArray);
+
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+
+    if (byteArray) {
+        env->DeleteLocalRef(byteArray);
+    }
+    return Void();
+}
+
+jbyteArray AGnssCallback_V1_0::convertToIpV4(uint32_t ip) {
+    if (INADDR_NONE == ip) {
+        return nullptr;
+    }
+
+    JNIEnv* env = getJniEnv();
+    jbyteArray byteArray = env->NewByteArray(4);
+    if (byteArray == nullptr) {
+        ALOGE("Unable to allocate byte array for IPv4 address");
+        return nullptr;
+    }
+
+    jbyte ipv4[4];
+    ALOGV("Converting IPv4 address byte array (net_order) %x", ip);
+    memcpy(ipv4, &ip, sizeof(ipv4));
+    env->SetByteArrayRegion(byteArray, 0, 4, (const jbyte*)ipv4);
+    return byteArray;
+}
+
+Return<void> AGnssCallback_V2_0::agnssStatusCb(IAGnssCallback_V2_0::AGnssType type,
+                                               IAGnssCallback_V2_0::AGnssStatusValue status) {
+    AGnssCallbackUtil::agnssStatusCbImpl(type, status);
+    return Void();
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/AGnssCallback.h b/services/core/jni/gnss/AGnssCallback.h
new file mode 100644
index 0000000..e9bb471
--- /dev/null
+++ b/services/core/jni/gnss/AGnssCallback.h
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_AGNSSCALLBACK_H
+#define _ANDROID_SERVER_GNSS_AGNSSCALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IAGnss.h>
+#include <android/hardware/gnss/2.0/IAGnss.h>
+#include <android/hardware/gnss/BnAGnssCallback.h>
+#include <arpa/inet.h>
+#include <log/log.h>
+
+#include <vector>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+
+extern jmethodID method_reportAGpsStatus;
+
+}
+
+void AGnss_class_init_once(JNIEnv* env, jclass clazz);
+
+/*
+ * AGnssCallbackAidl class implements the callback methods required by the
+ * android::hardware::gnss::IAGnss interface.
+ */
+class AGnssCallbackAidl : public android::hardware::gnss::BnAGnssCallback {
+public:
+    binder::Status agnssStatusCb(AGnssType type, AGnssStatusValue status) override;
+};
+
+/*
+ * AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface.
+ */
+class AGnssCallback_V1_0 : public android::hardware::gnss::V1_0::IAGnssCallback {
+public:
+    // Methods from ::android::hardware::gps::V1_0::IAGnssCallback follow.
+    hardware::Return<void> agnssStatusIpV6Cb(
+            const android::hardware::gnss::V1_0::IAGnssCallback::AGnssStatusIpV6& agps_status)
+            override;
+
+    hardware::Return<void> agnssStatusIpV4Cb(
+            const android::hardware::gnss::V1_0::IAGnssCallback::AGnssStatusIpV4& agps_status)
+            override;
+
+private:
+    jbyteArray convertToIpV4(uint32_t ip);
+};
+
+/*
+ * AGnssCallback_V2_0 implements callback methods required by the IAGnssCallback 2.0 interface.
+ */
+class AGnssCallback_V2_0 : public android::hardware::gnss::V2_0::IAGnssCallback {
+public:
+    // Methods from ::android::hardware::gps::V2_0::IAGnssCallback follow.
+    hardware::Return<void> agnssStatusCb(
+            android::hardware::gnss::V2_0::IAGnssCallback::AGnssType type,
+            android::hardware::gnss::V2_0::IAGnssCallback::AGnssStatusValue status) override;
+};
+
+class AGnssCallback {
+public:
+    AGnssCallback() {}
+    sp<AGnssCallbackAidl> getAidl() {
+        if (callbackAidl == nullptr) {
+            callbackAidl = sp<AGnssCallbackAidl>::make();
+        }
+        return callbackAidl;
+    }
+
+    sp<AGnssCallback_V1_0> getV1_0() {
+        if (callbackV1_0 == nullptr) {
+            callbackV1_0 = sp<AGnssCallback_V1_0>::make();
+        }
+        return callbackV1_0;
+    }
+
+    sp<AGnssCallback_V2_0> getV2_0() {
+        if (callbackV2_0 == nullptr) {
+            callbackV2_0 = sp<AGnssCallback_V2_0>::make();
+        }
+        return callbackV2_0;
+    }
+
+private:
+    sp<AGnssCallbackAidl> callbackAidl;
+    sp<AGnssCallback_V1_0> callbackV1_0;
+    sp<AGnssCallback_V2_0> callbackV2_0;
+};
+
+struct AGnssCallbackUtil {
+    template <class T, class U>
+    static void agnssStatusCbImpl(const T& type, const U& status);
+
+private:
+    AGnssCallbackUtil() = delete;
+};
+
+template <class T, class U>
+void AGnssCallbackUtil::agnssStatusCbImpl(const T& type, const U& status) {
+    ALOGD("%s. type: %d, status:%d", __func__, static_cast<int32_t>(type),
+          static_cast<int32_t>(status));
+    JNIEnv* env = getJniEnv();
+    env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, type, status, nullptr);
+    checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_AGNSSCALLBACK_H
\ No newline at end of file
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index ac50bfa..e49e81c 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -23,6 +23,8 @@
     ],
 
     srcs: [
+        "AGnss.cpp",
+        "AGnssCallback.cpp",
         "GnssAntennaInfoCallback.cpp",
         "GnssBatching.cpp",
         "GnssBatchingCallback.cpp",
diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h
index 1bd69c4..2640a77 100644
--- a/services/core/jni/gnss/Utils.h
+++ b/services/core/jni/gnss/Utils.h
@@ -196,6 +196,30 @@
     JNIEnv* mEnv = nullptr;
 };
 
+struct ScopedJniString {
+    ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
+        mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
+    }
+
+    ~ScopedJniString() {
+        if (mNativeString != nullptr) {
+            mEnv->ReleaseStringUTFChars(mJavaString, mNativeString);
+        }
+    }
+
+    const char* c_str() const { return mNativeString; }
+
+    operator hardware::hidl_string() const { return hardware::hidl_string(mNativeString); }
+
+private:
+    ScopedJniString(const ScopedJniString&) = delete;
+    ScopedJniString& operator=(const ScopedJniString&) = delete;
+
+    JNIEnv* mEnv;
+    jstring mJavaString;
+    const char* mNativeString;
+};
+
 JNIEnv* getJniEnv();
 
 template <class T>
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index ff61abc..d339ef1 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -63,6 +63,7 @@
 int register_android_server_GpuService(JNIEnv* env);
 int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
+int register_android_server_companion_virtual_InputController(JNIEnv* env);
 };
 
 using namespace android;
@@ -119,5 +120,6 @@
     register_android_server_GpuService(env);
     register_android_server_stats_pull_StatsPullAtomService(env);
     register_android_server_sensor_SensorService(vm, env);
+    register_android_server_companion_virtual_InputController(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 98a7b5e..23b685f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -91,18 +91,18 @@
 import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_ERROR_FAILURE_SETTING;
 import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_PRE_CONDITION_FAILED;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_PROFILE_CREATION_FAILED;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED;
-import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED;
 import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE;
 import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA;
 import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY;
+import static android.app.admin.ProvisioningException.ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED;
+import static android.app.admin.ProvisioningException.ERROR_PRE_CONDITION_FAILED;
+import static android.app.admin.ProvisioningException.ERROR_PROFILE_CREATION_FAILED;
+import static android.app.admin.ProvisioningException.ERROR_REMOVE_NON_REQUIRED_APPS_FAILED;
+import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
+import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
+import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
@@ -9008,42 +9008,48 @@
             return;
         }
 
+        Preconditions.checkCallAuthorization(
+                hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
         final CallerIdentity caller = getCallerIdentity();
-        if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
-                && getManagedUserId(userHandle) == -1
-                && newState != STATE_USER_UNMANAGED) {
-            // No managed device, user or profile, so setting provisioning state makes no sense.
-            throw new IllegalStateException("Not allowed to change provisioning state unless a "
-                      + "device or profile owner is set.");
-        }
+        final long id = mInjector.binderClearCallingIdentity();
+        try {
+            if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
+                    && getManagedUserId(userHandle) == -1
+                    && newState != STATE_USER_UNMANAGED) {
+                // No managed device, user or profile, so setting provisioning state makes no sense.
+                throw new IllegalStateException("Not allowed to change provisioning state unless a "
+                        + "device or profile owner is set.");
+            }
 
-        synchronized (getLockObject()) {
-            boolean transitionCheckNeeded = true;
+            synchronized (getLockObject()) {
+                boolean transitionCheckNeeded = true;
 
-            // Calling identity/permission checks.
-            if (isAdb(caller)) {
-                // ADB shell can only move directly from un-managed to finalized as part of directly
-                // setting profile-owner or device-owner.
-                if (getUserProvisioningState(userHandle) !=
-                        DevicePolicyManager.STATE_USER_UNMANAGED
-                        || newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
-                    throw new IllegalStateException("Not allowed to change provisioning state "
-                            + "unless current provisioning state is unmanaged, and new state is "
-                            + "finalized.");
+                // Calling identity/permission checks.
+                if (isAdb(caller)) {
+                    // ADB shell can only move directly from un-managed to finalized as part of
+                    // directly setting profile-owner or device-owner.
+                    if (getUserProvisioningState(userHandle)
+                            != DevicePolicyManager.STATE_USER_UNMANAGED
+                            || newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
+                        throw new IllegalStateException("Not allowed to change provisioning state "
+                                + "unless current provisioning state is unmanaged, and new state"
+                                + "is finalized.");
+                    }
+                    transitionCheckNeeded = false;
                 }
-                transitionCheckNeeded = false;
-            } else {
-                Preconditions.checkCallAuthorization(
-                        hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
-            }
 
-            final DevicePolicyData policyData = getUserData(userHandle);
-            if (transitionCheckNeeded) {
-                // Optional state transition check for non-ADB case.
-                checkUserProvisioningStateTransition(policyData.mUserProvisioningState, newState);
+                final DevicePolicyData policyData = getUserData(userHandle);
+                if (transitionCheckNeeded) {
+                    // Optional state transition check for non-ADB case.
+                    checkUserProvisioningStateTransition(policyData.mUserProvisioningState,
+                            newState);
+                }
+                policyData.mUserProvisioningState = newState;
+                saveSettingsLocked(userHandle);
             }
-            policyData.mUserProvisioningState = newState;
-            saveSettingsLocked(userHandle);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
         }
     }
 
@@ -10647,15 +10653,6 @@
         }
 
         final int userHandle = user.getIdentifier();
-        final Intent intent = new Intent(DevicePolicyManager.ACTION_MANAGED_USER_CREATED)
-                .putExtra(Intent.EXTRA_USER_HANDLE, userHandle)
-                .putExtra(
-                        DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
-                        leaveAllSystemAppsEnabled)
-                .setPackage(getManagedProvisioningPackage(mContext))
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-
         final long id = mInjector.binderClearCallingIdentity();
         try {
             manageUserUnchecked(admin, profileOwner, userHandle, adminExtras,
@@ -10666,6 +10663,9 @@
                         Settings.Secure.USER_SETUP_COMPLETE, 1, userHandle);
             }
 
+            sendProvisioningCompletedBroadcast(
+                    userHandle, ACTION_PROVISION_MANAGED_USER, leaveAllSystemAppsEnabled);
+
             return user;
         } catch (Throwable re) {
             mUserManager.removeUser(userHandle);
@@ -10680,6 +10680,20 @@
         }
     }
 
+    private void sendProvisioningCompletedBroadcast(
+            int user, String action, boolean leaveAllSystemAppsEnabled) {
+        final Intent intent = new Intent(DevicePolicyManager.ACTION_PROVISIONING_COMPLETED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, user)
+                .putExtra(
+                        DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
+                        leaveAllSystemAppsEnabled)
+                .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_ACTION,
+                        action)
+                .setPackage(getManagedProvisioningPackage(mContext))
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+    }
+
     private void manageUserUnchecked(ComponentName admin, ComponentName profileOwner,
             @UserIdInt int userId, @Nullable PersistableBundle adminExtras,
             boolean showDisclaimer) {
@@ -17224,7 +17238,7 @@
                     ACTION_PROVISION_MANAGED_PROFILE, admin.getPackageName());
             if (result != CODE_OK) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_PRE_CONDITION_FAILED,
+                        ERROR_PRE_CONDITION_FAILED,
                         "Provisioning preconditions failed with result: " + result);
             }
 
@@ -17241,7 +17255,7 @@
                     nonRequiredApps.toArray(new String[nonRequiredApps.size()]));
             if (userInfo == null) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_PROFILE_CREATION_FAILED,
+                        ERROR_PROFILE_CREATION_FAILED,
                         "Error creating profile, createProfileForUserEvenWhenDisallowed "
                                 + "returned null.");
             }
@@ -17255,7 +17269,7 @@
             if (!enableAdminAndSetProfileOwner(
                     userInfo.id, caller.getUserId(), admin, provisioningParams.getOwnerName())) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED,
+                        ERROR_SETTING_PROFILE_OWNER_FAILED,
                         "Error setting profile owner.");
             }
             setUserSetupComplete(userInfo.id);
@@ -17263,7 +17277,7 @@
             startUser(userInfo.id, callerPackage);
             maybeMigrateAccount(
                     userInfo.id, caller.getUserId(), provisioningParams.getAccountToMigrate(),
-                    provisioningParams.isKeepAccountMigrated(), callerPackage);
+                    provisioningParams.isKeepingAccountOnMigration(), callerPackage);
 
             if (provisioningParams.isOrganizationOwnedProvisioning()) {
                 synchronized (getLockObject()) {
@@ -17271,6 +17285,11 @@
                 }
             }
 
+            sendProvisioningCompletedBroadcast(
+                    userInfo.id,
+                    ACTION_PROVISION_MANAGED_PROFILE,
+                    provisioningParams.isLeaveAllSystemAppsEnabled());
+
             return userInfo.getUserHandle();
         } catch (Exception e) {
             DevicePolicyEventLogger
@@ -17330,14 +17349,14 @@
                     userId);
             if (status != PackageManager.INSTALL_SUCCEEDED) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED,
+                        ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED,
                         String.format("Failed to install existing package %s for user %d with "
                                         + "result code %d",
                                 packageName, userId, status));
             }
         } catch (NameNotFoundException e) {
             throw new ServiceSpecificException(
-                    PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED,
+                    ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED,
                     String.format("Failed to install existing package %s for user %d: %s",
                             packageName, userId, e.getMessage()));
         }
@@ -17395,12 +17414,12 @@
                 /* scheduler= */ null);
         try {
             if (!mInjector.getIActivityManager().startUserInBackground(userId)) {
-                throw new ServiceSpecificException(PROVISIONING_RESULT_STARTING_PROFILE_FAILED,
+                throw new ServiceSpecificException(ERROR_STARTING_PROFILE_FAILED,
                         String.format("Unable to start user %d in background", userId));
             }
 
             if (!unlockedReceiver.waitForUserUnlocked()) {
-                throw new ServiceSpecificException(PROVISIONING_RESULT_STARTING_PROFILE_FAILED,
+                throw new ServiceSpecificException(ERROR_STARTING_PROFILE_FAILED,
                         String.format("Timeout whilst waiting for unlock of user %d.", userId));
             }
             logEventDuration(
@@ -17523,7 +17542,7 @@
                     ACTION_PROVISION_MANAGED_DEVICE, deviceAdmin.getPackageName());
             if (result != CODE_OK) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_PRE_CONDITION_FAILED,
+                        ERROR_PRE_CONDITION_FAILED,
                         "Provisioning preconditions failed with result: " + result);
             }
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
@@ -17536,7 +17555,7 @@
                     provisioningParams.isLeaveAllSystemAppsEnabled(),
                     deviceAdmin)) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED,
+                        ERROR_REMOVE_NON_REQUIRED_APPS_FAILED,
                         "PackageManager failed to remove non required apps.");
             }
 
@@ -17544,13 +17563,17 @@
             if (!setActiveAdminAndDeviceOwner(
                     deviceOwnerUserId, deviceAdmin, provisioningParams.getOwnerName())) {
                 throw new ServiceSpecificException(
-                        PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED,
+                        ERROR_SET_DEVICE_OWNER_FAILED,
                         "Failed to set device owner.");
             }
 
             disallowAddUser();
             setAdminCanGrantSensorsPermissionForUserUnchecked(deviceOwnerUserId,
                     provisioningParams.canDeviceOwnerGrantSensorsPermissions());
+            sendProvisioningCompletedBroadcast(
+                    deviceOwnerUserId,
+                    ACTION_PROVISION_MANAGED_DEVICE,
+                    provisioningParams.isLeaveAllSystemAppsEnabled());
         } catch (Exception e) {
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.PLATFORM_PROVISIONING_ERROR)
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a72cf3a..663e17b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -151,6 +151,7 @@
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.people.PeopleService;
+import com.android.server.pm.ApexManager;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DataLoaderManagerService;
 import com.android.server.pm.DynamicCodeLoggingService;
@@ -220,6 +221,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Timer;
 import java.util.TreeSet;
 import java.util.concurrent.CountDownLatch;
@@ -332,6 +334,8 @@
             "com.android.server.contentcapture.ContentCaptureManagerService";
     private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
             "com.android.server.translation.TranslationManagerService";
+    private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS =
+            "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
     private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
     private static final String SYSTEM_CAPTIONS_MANAGER_SERVICE_CLASS =
@@ -916,6 +920,13 @@
             startBootstrapServices(t);
             startCoreServices(t);
             startOtherServices(t);
+            // Apex services must be the last category of services to start. No other service must
+            // be starting after this point. This is to prevent unnessary stability issues when
+            // these apexes are updated outside of OTA; and to avoid breaking dependencies from
+            // system into apexes.
+            // TODO(satayev): lock mSystemServiceManager.startService to stop accepting new services
+            // after this step
+            startApexServices(t);
         } catch (Throwable ex) {
             Slog.e("System", "******************************************");
             Slog.e("System", "************ Failure starting system services", ex);
@@ -2592,6 +2603,11 @@
             Slog.d(TAG, "TranslationService not defined by OEM");
         }
 
+        // Selection toolbar service
+        t.traceBegin("StartSelectionToolbarManagerService");
+        mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
+        t.traceEnd();
+
         // NOTE: ClipboardService depends on ContentCapture and Autofill
         t.traceBegin("StartClipboardService");
         mSystemServiceManager.startService(ClipboardService.class);
@@ -3044,6 +3060,27 @@
         t.traceEnd(); // startOtherServices
     }
 
+    /**
+     * Starts system services defined in apexes.
+     */
+    private void startApexServices(@NonNull TimingsTraceAndSlog t) {
+        t.traceBegin("startApexServices");
+        Map<String, String> services = ApexManager.getInstance().getApexSystemServices();
+        // TODO(satayev): filter out already started services
+        // TODO(satayev): introduce android:order for services coming the same apexes
+        for (String name : new TreeSet<>(services.keySet())) {
+            String jarPath = services.get(name);
+            t.traceBegin("starting " + name);
+            if (TextUtils.isEmpty(jarPath)) {
+                mSystemServiceManager.startService(name);
+            } else {
+                mSystemServiceManager.startServiceFromJar(name, jarPath);
+            }
+            t.traceEnd();
+        }
+        t.traceEnd(); // startApexServices
+    }
+
     private boolean deviceHasConfigString(@NonNull Context context, @StringRes int resId) {
         String serviceName = context.getString(resId);
         return !TextUtils.isEmpty(serviceName);
diff --git a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java
index 49d5e50..d3353cd 100644
--- a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java
+++ b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java
@@ -31,6 +31,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.people.PeopleServiceInternal;
+import com.android.server.pm.PackageManagerService;
 
 /**
  * If a {@link ConversationStatus} is added to the system with an expiration time, remove that
@@ -50,6 +51,7 @@
             final PendingIntent pi = PendingIntent.getBroadcast(context,
                     REQUEST_CODE,
                     new Intent(ACTION)
+                            .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
                             .setData(new Uri.Builder().scheme(SCHEME)
                                     .appendPath(getKey(userId, pkg, conversationId, status))
                                     .build())
diff --git a/services/proguard.flags b/services/proguard.flags
new file mode 100644
index 0000000..30dd6cf
--- /dev/null
+++ b/services/proguard.flags
@@ -0,0 +1,11 @@
+# TODO(b/196084106): Refine and optimize this configuration. Note that this
+# configuration is only used when `SOONG_CONFIG_ANDROID_SYSTEM_OPTIMIZE_JAVA=true`.
+-keep,allowoptimization,allowaccessmodification class ** {
+  *;
+}
+
+# Various classes subclassed in ethernet-service (avoid marking final).
+-keep public class android.net.** { *; }
+
+# Referenced via CarServiceHelperService in car-frameworks-service (avoid removing).
+-keep public class com.android.server.utils.Slogf { *; }
\ No newline at end of file
diff --git a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
index bf4eeae..8561651 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -43,13 +43,13 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.BackupManagerService;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.testing.TransportData;
 import com.android.server.backup.testing.TransportTestUtils;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.testing.shadows.ShadowSlog;
 
@@ -75,7 +75,7 @@
     @Mock private UserBackupManagerService mBackupManagerService;
     @Mock private TransportManager mTransportManager;
     @Mock private OnTaskFinishedListener mListener;
-    @Mock private IBackupTransport mTransportBinder;
+    @Mock private BackupTransportClient mTransportClient;
     @Mock private IBackupObserver mObserver;
     @Mock private AlarmManager mAlarmManager;
     @Mock private PendingIntent mRunInitIntent;
@@ -101,19 +101,19 @@
     @Test
     public void testRun_callsTransportCorrectly() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransportBinder).initializeDevice();
-        verify(mTransportBinder).finishBackup();
+        verify(mTransportClient).initializeDevice();
+        verify(mTransportClient).finishBackup();
     }
 
     @Test
     public void testRun_callsBackupManagerCorrectly() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -127,7 +127,7 @@
     @Test
     public void testRun_callsObserverAndListenerCorrectly() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -140,13 +140,13 @@
     @Test
     public void testRun_whenInitializeDeviceFails() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransportBinder).initializeDevice();
-        verify(mTransportBinder, never()).finishBackup();
+        verify(mTransportClient).initializeDevice();
+        verify(mTransportClient, never()).finishBackup();
         verify(mBackupManagerService)
                 .recordInitPending(true, mTransportName, mTransport.transportDirName);
     }
@@ -155,7 +155,7 @@
     public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
             throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -168,7 +168,7 @@
     @Test
     public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -179,13 +179,13 @@
     @Test
     public void testRun_whenFinishBackupFails() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransportBinder).initializeDevice();
-        verify(mTransportBinder).finishBackup();
+        verify(mTransportClient).initializeDevice();
+        verify(mTransportClient).finishBackup();
         verify(mBackupManagerService)
                 .recordInitPending(true, mTransportName, mTransport.transportDirName);
     }
@@ -193,7 +193,7 @@
     @Test
     public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -206,7 +206,7 @@
     @Test
     public void testRun_whenFinishBackupFails_logs() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -219,7 +219,7 @@
     @Test
     public void testRun_whenInitializeDeviceFails_logs() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -232,7 +232,7 @@
     @Test
     public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
         setUpTransport(mTransport);
-        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
@@ -327,7 +327,7 @@
         List<TransportMock> transportMocks =
                 setUpTransports(mTransportManager, transport1, transport2);
         String registeredTransportName = transport2.transportName;
-        IBackupTransport registeredTransport = transportMocks.get(1).transport;
+        BackupTransportClient registeredTransport = transportMocks.get(1).transport;
         TransportConnection
                 registeredTransportConnection = transportMocks.get(1).mTransportConnection;
         PerformInitializeTask performInitializeTask =
@@ -357,7 +357,7 @@
     @Test
     public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
         TransportMock transportMock = setUpTransport(mTransport);
-        IBackupTransport transport = transportMock.transport;
+        BackupTransportClient transport = transportMock.transport;
         TransportConnection transportConnection = transportMock.mTransportConnection;
         when(transport.initializeDevice()).thenThrow(DeadObjectException.class);
         PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
@@ -380,7 +380,7 @@
     }
 
     private void configureTransport(
-            IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus)
+            BackupTransportClient transportMock, int initializeDeviceStatus, int finishBackupStatus)
             throws Exception {
         when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus);
         when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
@@ -389,7 +389,7 @@
     private TransportMock setUpTransport(TransportData transport) throws Exception {
         TransportMock transportMock =
                 TransportTestUtils.setUpTransport(mTransportManager, transport);
-        mTransportBinder = transportMock.transport;
+        mTransportClient = transportMock.transport;
         return transportMock;
     }
 }
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 fd295c0..9e83f8e 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
@@ -278,7 +278,7 @@
 
         assertThat(mBackupManagerService.getPendingInits()).isEmpty();
         assertThat(mBackupManagerService.isBackupRunning()).isFalse();
-        assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+        assertThat(mBackupManagerService.getOperationStorage().numOperations()).isEqualTo(0);
         verify(mOldJournal).delete();
     }
 
@@ -449,7 +449,7 @@
 
         assertThat(mBackupManagerService.getPendingInits()).isEmpty();
         assertThat(mBackupManagerService.isBackupRunning()).isFalse();
-        assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
+        assertThat(mBackupManagerService.getOperationStorage().numOperations()).isEqualTo(0);
         assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(1234L);
         verify(mBackupManagerService).writeRestoreTokens();
         verify(mOldJournal).delete();
@@ -2665,6 +2665,7 @@
         KeyValueBackupTask task =
                 new KeyValueBackupTask(
                         mBackupManagerService,
+                        mBackupManagerService.getOperationStorage(),
                         transportMock.mTransportConnection,
                         transportMock.transportData.transportDirName,
                         queue,
diff --git a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
index 9eb99ae..e0812d6 100644
--- a/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java
@@ -51,6 +51,7 @@
 
 import com.android.server.EventLogTags;
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.BackupHandler;
@@ -98,6 +99,7 @@
     @Mock private IRestoreObserver mObserver;
     @Mock private IBackupManagerMonitor mMonitor;
     @Mock private BackupEligibilityRules mBackupEligibilityRules;
+    @Mock private OperationStorage mOperationStorage;
     private ShadowLooper mShadowBackupLooper;
     private ShadowApplication mShadowApplication;
     private UserBackupManagerService.BackupWakeLock mWakeLock;
@@ -132,7 +134,9 @@
         // We need to mock BMS timeout parameters before initializing the BackupHandler since
         // the constructor of BackupHandler relies on it.
         when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters);
-        BackupHandler backupHandler = new BackupHandler(mBackupManagerService, handlerThread);
+
+        BackupHandler backupHandler =
+                new BackupHandler(mBackupManagerService, mOperationStorage, handlerThread);
 
         mWakeLock = createBackupWakeLock(application);
 
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
index 77b5b61..fc3ec7b 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java
@@ -120,7 +120,6 @@
         when(backupManagerService.getTransportManager()).thenReturn(transportManager);
         when(backupManagerService.getPackageManager()).thenReturn(packageManager);
         when(backupManagerService.getBackupHandler()).thenReturn(backupHandler);
-        when(backupManagerService.getCurrentOpLock()).thenReturn(new Object());
         when(backupManagerService.getQueueLock()).thenReturn(new Object());
         when(backupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
         when(backupManagerService.getWakelock()).thenReturn(wakeLock);
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
index ce44f06..8131ac4 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -34,8 +34,8 @@
 import android.content.pm.ServiceInfo;
 import android.os.RemoteException;
 
-import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -160,7 +160,7 @@
             when(transportConnectionMock.getTransportComponent()).thenReturn(transportComponent);
             if (status == TransportStatus.REGISTERED_AVAILABLE) {
                 // Transport registered and available
-                IBackupTransport transportMock = mockTransportBinder(transport);
+                BackupTransportClient transportMock = mockTransportBinder(transport);
                 when(transportConnectionMock.connectOrThrow(any())).thenReturn(transportMock);
                 when(transportConnectionMock.connect(any())).thenReturn(transportMock);
 
@@ -179,8 +179,9 @@
         }
     }
 
-    private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception {
-        IBackupTransport transportBinder = mock(IBackupTransport.class);
+    private static BackupTransportClient mockTransportBinder(TransportData transport)
+            throws Exception {
+        BackupTransportClient transportBinder = mock(BackupTransportClient.class);
         try {
             when(transportBinder.name()).thenReturn(transport.transportName);
             when(transportBinder.transportDirName()).thenReturn(transport.transportDirName);
@@ -199,12 +200,12 @@
     public static class TransportMock {
         public final TransportData transportData;
         @Nullable public final TransportConnection mTransportConnection;
-        @Nullable public final IBackupTransport transport;
+        @Nullable public final BackupTransportClient transport;
 
         private TransportMock(
                 TransportData transportData,
                 @Nullable TransportConnection transportConnection,
-                @Nullable IBackupTransport transport) {
+                @Nullable BackupTransportClient transport) {
             this.transportData = transportData;
             this.mTransportConnection = transportConnection;
             this.transport = transport;
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index de4aec6..6a82f16 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
@@ -84,9 +84,11 @@
     @Mock private TransportConnectionListener mTransportConnectionListener;
     @Mock private TransportConnectionListener mTransportConnectionListener2;
     @Mock private IBackupTransport.Stub mTransportBinder;
+
     @UserIdInt private int mUserId;
     private TransportStats mTransportStats;
     private TransportConnection mTransportConnection;
+    private BackupTransportClient mTransportClient;
     private ComponentName mTransportComponent;
     private String mTransportString;
     private Intent mBindIntent;
@@ -116,6 +118,7 @@
                         "1",
                         "caller",
                         new Handler(mainLooper));
+        mTransportClient = new BackupTransportClient(mTransportBinder);
 
         when(mContext.bindServiceAsUser(
                         eq(mBindIntent),
@@ -156,7 +159,8 @@
 
         mShadowMainLooper.runToEndOfTasks();
         verify(mTransportConnectionListener)
-                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+                .onTransportConnectionResult(any(BackupTransportClient.class),
+                        eq(mTransportConnection));
     }
 
     @Test
@@ -169,9 +173,11 @@
         connection.onServiceConnected(mTransportComponent, mTransportBinder);
         mShadowMainLooper.runToEndOfTasks();
         verify(mTransportConnectionListener)
-                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+                .onTransportConnectionResult(any(BackupTransportClient.class),
+                        eq(mTransportConnection));
         verify(mTransportConnectionListener2)
-                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+                .onTransportConnectionResult(any(BackupTransportClient.class),
+                        eq(mTransportConnection));
     }
 
     @Test
@@ -184,7 +190,8 @@
 
         mShadowMainLooper.runToEndOfTasks();
         verify(mTransportConnectionListener2)
-                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+                .onTransportConnectionResult(any(BackupTransportClient.class),
+                        eq(mTransportConnection));
     }
 
     @Test
@@ -312,10 +319,10 @@
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
         connection.onServiceConnected(mTransportComponent, mTransportBinder);
 
-        IBackupTransport transportBinder =
+        BackupTransportClient transportClient =
                 runInWorkerThread(() -> mTransportConnection.connect("caller2"));
 
-        assertThat(transportBinder).isNotNull();
+        assertThat(transportClient).isNotNull();
     }
 
     @Test
@@ -325,10 +332,10 @@
         connection.onServiceConnected(mTransportComponent, mTransportBinder);
         connection.onServiceDisconnected(mTransportComponent);
 
-        IBackupTransport transportBinder =
+        BackupTransportClient transportClient =
                 runInWorkerThread(() -> mTransportConnection.connect("caller2"));
 
-        assertThat(transportBinder).isNull();
+        assertThat(transportClient).isNull();
     }
 
     @Test
@@ -337,10 +344,10 @@
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
         connection.onBindingDied(mTransportComponent);
 
-        IBackupTransport transportBinder =
+        BackupTransportClient transportClient =
                 runInWorkerThread(() -> mTransportConnection.connect("caller2"));
 
-        assertThat(transportBinder).isNull();
+        assertThat(transportClient).isNull();
     }
 
     @Test
@@ -354,17 +361,17 @@
         doAnswer(
                         invocation -> {
                             TransportConnectionListener listener = invocation.getArgument(0);
-                            listener.onTransportConnectionResult(mTransportBinder,
+                            listener.onTransportConnectionResult(mTransportClient,
                                     transportConnection);
                             return null;
                         })
                 .when(transportConnection)
                 .connectAsync(any(), any());
 
-        IBackupTransport transportBinder =
+        BackupTransportClient transportClient =
                 runInWorkerThread(() -> transportConnection.connect("caller"));
 
-        assertThat(transportBinder).isNotNull();
+        assertThat(transportClient).isNotNull();
     }
 
     @Test
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
index 06b7fb7..6a7d031 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupTask.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 import com.android.server.backup.DataChangedJournal;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.keyvalue.KeyValueBackupReporter;
@@ -56,6 +57,7 @@
     @Implementation
     protected void __constructor__(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             String transportDirName,
             List<String> queue,
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
index 71010a9..d985e1b 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowPerformUnifiedRestoreTask.java
@@ -21,6 +21,7 @@
 import android.app.backup.IRestoreObserver;
 import android.content.pm.PackageInfo;
 
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
@@ -57,6 +58,7 @@
     @Implementation
     protected void __constructor__(
             UserBackupManagerService backupManagerService,
+            OperationStorage operationStorage,
             TransportConnection transportConnection,
             IRestoreObserver observer,
             IBackupManagerMonitor monitor,
diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp
new file mode 100644
index 0000000..cc6405f
--- /dev/null
+++ b/services/selectiontoolbar/Android.bp
@@ -0,0 +1,22 @@
+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.selectiontoolbar-sources",
+    srcs: ["java/**/*.java"],
+    path: "java",
+    visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+    name: "services.selectiontoolbar",
+    defaults: ["platform_service_defaults"],
+    srcs: [":services.selectiontoolbar-sources"],
+    libs: ["services.core"],
+}
diff --git a/services/selectiontoolbar/OWNERS b/services/selectiontoolbar/OWNERS
new file mode 100644
index 0000000..ed9425c
--- /dev/null
+++ b/services/selectiontoolbar/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/selectiontoolbar/OWNERS
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
new file mode 100644
index 0000000..c26965d
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/DefaultSelectionToolbarRenderService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.selectiontoolbar;
+
+import android.service.selectiontoolbar.SelectionToolbarRenderService;
+import android.util.Log;
+import android.view.selectiontoolbar.ShowInfo;
+
+/**
+ * The default implementation of {@link SelectionToolbarRenderService}.
+ */
+public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
+
+    private static final String TAG = "DefaultSelectionToolbarRenderService";
+
+    @Override
+    public void onShow(ShowInfo showInfo,
+            SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) {
+        // TODO: Add implementation
+        Log.w(TAG, "onShow()");
+    }
+
+    @Override
+    public void onHide(long widgetToken) {
+        // TODO: Add implementation
+        Log.w(TAG, "onHide()");
+    }
+
+    @Override
+    public void onDismiss(long widgetToken) {
+        // TODO: Add implementation
+        Log.w(TAG, "onDismiss()");
+    }
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
new file mode 100644
index 0000000..ced24e0
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
@@ -0,0 +1,68 @@
+/*
+ * 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.selectiontoolbar;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.service.selectiontoolbar.ISelectionToolbarRenderService;
+import android.service.selectiontoolbar.SelectionToolbarRenderService;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+
+import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.ServiceConnector;
+
+final class RemoteSelectionToolbarRenderService extends
+        ServiceConnector.Impl<ISelectionToolbarRenderService> {
+    private static final String TAG = "RemoteSelectionToolbarRenderService";
+
+    private static final long TIMEOUT_IDLE_UNBIND_MS =
+            AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
+
+    private final ComponentName mComponentName;
+
+
+    RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId) {
+        super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent(
+                serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface);
+        mComponentName = serviceName;
+        // Bind right away.
+        connect();
+    }
+
+    @Override // from AbstractRemoteService
+    protected long getAutoDisconnectTimeoutMs() {
+        return TIMEOUT_IDLE_UNBIND_MS;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public void onShow(ShowInfo showInfo, ISelectionToolbarCallback callback) {
+        run((s) -> s.onShow(showInfo, callback));
+    }
+
+    public void onHide(long widgetToken) {
+        run((s) -> s.onHide(widgetToken));
+    }
+
+    public void onDismiss(long widgetToken) {
+        run((s) -> s.onDismiss(widgetToken));
+    }
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java
new file mode 100644
index 0000000..3bdf55c
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java
@@ -0,0 +1,104 @@
+/*
+ * 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.selectiontoolbar;
+
+import android.content.Context;
+import android.util.Slog;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ISelectionToolbarManager;
+import android.view.selectiontoolbar.ShowInfo;
+
+import com.android.internal.util.DumpUtils;
+import com.android.server.infra.AbstractMasterSystemService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Entry point service for selection toolbar management.
+ */
+public final class SelectionToolbarManagerService extends
+        AbstractMasterSystemService<SelectionToolbarManagerService,
+                SelectionToolbarManagerServiceImpl> {
+
+    private static final String TAG = "SelectionToolbarManagerService";
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.SELECTION_TOOLBAR_SERVICE,
+                new SelectionToolbarManagerService.SelectionToolbarManagerServiceStub());
+    }
+
+    public SelectionToolbarManagerService(Context context) {
+        super(context, new SelectionToolbarServiceNameResolver(), /* disallowProperty= */
+                null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
+    }
+
+    @Override
+    protected SelectionToolbarManagerServiceImpl newServiceLocked(int resolvedUserId,
+            boolean disabled) {
+        return new SelectionToolbarManagerServiceImpl(this, mLock, resolvedUserId);
+    }
+
+    final class SelectionToolbarManagerServiceStub extends ISelectionToolbarManager.Stub {
+
+        @Override
+        public void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback, int userId) {
+            synchronized (mLock) {
+                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.showToolbar(showInfo, callback);
+                } else {
+                    Slog.v(TAG, "showToolbar(): no service for " + userId);
+                }
+            }
+        }
+
+        @Override
+        public void hideToolbar(long widgetToken, int userId) {
+            synchronized (mLock) {
+                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.hideToolbar(widgetToken);
+                } else {
+                    Slog.v(TAG, "hideToolbar(): no service for " + userId);
+                }
+            }
+        }
+
+        @Override
+        public void dismissToolbar(long widgetToken, int userId) {
+            synchronized (mLock) {
+                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
+                if (service != null) {
+                    service.dismissToolbar(widgetToken);
+                } else {
+                    Slog.v(TAG, "dismissToolbar(): no service for " + userId);
+                }
+            }
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
+
+            synchronized (mLock) {
+                dumpLocked("", pw);
+            }
+        }
+    }
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
new file mode 100644
index 0000000..235f547
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
@@ -0,0 +1,128 @@
+/*
+ * 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.selectiontoolbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.selectiontoolbar.ISelectionToolbarCallback;
+import android.view.selectiontoolbar.ShowInfo;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.infra.AbstractPerUserSystemService;
+
+final class SelectionToolbarManagerServiceImpl extends
+        AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl,
+                SelectionToolbarManagerService> {
+
+    private static final String TAG = "SelectionToolbarManagerServiceImpl";
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSelectionToolbarRenderService mRemoteService;
+
+    protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master,
+            @NonNull Object lock, int userId) {
+        super(master, lock, userId);
+        updateRemoteServiceLocked();
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        return getServiceInfoOrThrow(serviceComponent, mUserId);
+    }
+
+    @GuardedBy("mLock")
+    @Override // from PerUserSystemService
+    protected boolean updateLocked(boolean disabled) {
+        final boolean enabledChanged = super.updateLocked(disabled);
+        updateRemoteServiceLocked();
+        return enabledChanged;
+    }
+
+    /**
+     * Updates the reference to the remote service.
+     */
+    @GuardedBy("mLock")
+    private void updateRemoteServiceLocked() {
+        if (mRemoteService != null) {
+            Slog.d(TAG, "updateRemoteService(): destroying old remote service");
+            mRemoteService.unbind();
+            mRemoteService = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) {
+        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onShow(showInfo, callback);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void hideToolbar(long widgetToken) {
+        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onHide(widgetToken);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void dismissToolbar(long widgetToken) {
+        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
+        if (remoteService != null) {
+            remoteService.onDismiss(widgetToken);
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private RemoteSelectionToolbarRenderService ensureRemoteServiceLocked() {
+        if (mRemoteService == null) {
+            final String serviceName = getComponentNameLocked();
+            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
+            mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent,
+                    mUserId);
+        }
+        return mRemoteService;
+    }
+
+    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
+        int flags = PackageManager.GET_META_DATA;
+
+        ServiceInfo si = null;
+        try {
+            si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId);
+        } catch (RemoteException e) {
+        }
+        if (si == null) {
+            throw new PackageManager.NameNotFoundException("Could not get serviceInfo for "
+                    + comp.flattenToShortString());
+        }
+        return si;
+    }
+}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
new file mode 100644
index 0000000..1d4c94d
--- /dev/null
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
@@ -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.selectiontoolbar;
+
+import com.android.server.infra.ServiceNameResolver;
+
+import java.io.PrintWriter;
+
+final class SelectionToolbarServiceNameResolver implements ServiceNameResolver {
+
+    // TODO: move to SysUi or ExtServices
+    private static final String SELECTION_TOOLBAR_SERVICE_NAME =
+            "android/com.android.server.selectiontoolbar.DefaultSelectionToolbarRenderService";
+
+    @Override
+    public String getDefaultServiceName(int userId) {
+        return SELECTION_TOOLBAR_SERVICE_NAME;
+    }
+
+    @Override
+    public void dumpShort(PrintWriter pw) {
+        pw.print("service="); pw.print(SELECTION_TOOLBAR_SERVICE_NAME);
+    }
+
+    @Override
+    public void dumpShort(PrintWriter pw, int userId) {
+        pw.print("defaultService="); pw.print(getDefaultServiceName(userId));
+    }
+}
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 4a35fdf..d7e3195 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
@@ -24,17 +24,7 @@
 import android.content.pm.PackageManager
 import android.content.pm.SigningDetails
 import android.content.pm.parsing.ParsingPackage
-import android.content.pm.parsing.component.ParsedActivityImpl
-import android.content.pm.parsing.component.ParsedAttributionImpl
-import android.content.pm.parsing.component.ParsedComponentImpl
-import android.content.pm.parsing.component.ParsedInstrumentationImpl
-import android.content.pm.parsing.component.ParsedIntentInfoImpl
-import android.content.pm.parsing.component.ParsedPermissionGroupImpl
-import android.content.pm.parsing.component.ParsedPermissionImpl
-import android.content.pm.parsing.component.ParsedProcessImpl
-import android.content.pm.parsing.component.ParsedProviderImpl
-import android.content.pm.parsing.component.ParsedServiceImpl
-import android.content.pm.parsing.component.ParsedUsesPermissionImpl
+import android.content.pm.parsing.component.*
 import android.net.Uri
 import android.os.Bundle
 import android.os.Parcelable
@@ -360,6 +350,13 @@
             transformSet = { ParsedActivityImpl().apply { name = it }.withMimeGroups() }
         ),
         getSetByValue(
+            AndroidPackage::getApexSystemServices,
+            PackageImpl::addApexSystemService,
+            "TestApexSystemServiceName",
+            transformGet = { it.singleOrNull()?.name.orEmpty() },
+            transformSet = { ParsedApexSystemServiceImpl().apply { name = it } }
+        ),
+        getSetByValue(
             AndroidPackage::getReceivers,
             PackageImpl::addReceiver,
             "TestReceiverName",
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index e633850..005d3e8 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.pm.parsing.component.ParsedProcess
 import android.content.pm.parsing.component.ParsedProcessImpl
+import android.util.ArrayMap
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
@@ -29,6 +30,8 @@
     override val excludedMethods = listOf(
         // Copying method
         "addStateFrom",
+        // Utility method
+        "putAppClassNameForPackage",
     )
 
     override val baseParams = listOf(
@@ -39,6 +42,9 @@
     )
 
     override fun extraParams() = listOf(
-        getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission"))
+        getter(ParsedProcess::getDeniedPermissions, setOf("testDeniedPermission")),
+        getter(ParsedProcess::getAppClassNamesByPackage, ArrayMap<String, String>().apply {
+            put("package1", "classname1");
+        }),
     )
 }
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 d1d7cc6..089e9db 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
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
 import android.content.pm.parsing.component.ParsedActivityImpl
 import android.content.pm.parsing.component.ParsedIntentInfoImpl
 import android.content.pm.verify.domain.DomainVerificationManager
@@ -26,6 +27,7 @@
 import android.os.Build
 import android.os.Process
 import android.util.ArraySet
+import android.util.IndentingPrintWriter
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.server.pm.parsing.pkg.AndroidPackage
@@ -46,6 +48,7 @@
 import org.mockito.Mockito.anyLong
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.verifyNoMoreInteractions
 import java.util.UUID
 import java.util.concurrent.atomic.AtomicBoolean
@@ -204,6 +207,14 @@
                 service(Type.QUERENT, "getInfo") {
                     getDomainVerificationInfo(it.targetPackageName)
                 },
+                service(Type.QUERENT, "printState") {
+                    printState(mock(IndentingPrintWriter::class.java), null, null)
+                },
+                service(Type.QUERENT, "printStateInternal") {
+                    printState(mock(IndentingPrintWriter::class.java), null, null) {
+                        mockPkgState(it, UUID.randomUUID())
+                    }
+                },
                 service(Type.VERIFIER, "setStatus") {
                     setDomainVerificationStatus(
                         it.targetDomainSetId,
@@ -311,6 +322,7 @@
                     }
                 )
             }
+            whenever(signingDetails) { SigningDetails.UNKNOWN }
         }
 
         fun mockPkgState(packageName: String, domainSetId: UUID) =
@@ -327,6 +339,7 @@
                     }
                 }
                 whenever(isSystem) { false }
+                whenever(signingDetails) { SigningDetails.UNKNOWN }
             }
     }
 
@@ -373,6 +386,7 @@
         val allowUserState = AtomicBoolean(false)
         val allowPreferredApps = AtomicBoolean(false)
         val allowQueryAll = AtomicBoolean(false)
+        val allowDump = AtomicBoolean(false)
         val context: Context = mockThrowOnUnmocked {
             initPermission(
                 allowUserState,
@@ -383,6 +397,7 @@
                 android.Manifest.permission.SET_PREFERRED_APPLICATIONS
             )
             initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES)
+            initPermission(allowDump, android.Manifest.permission.DUMP)
         }
         val target = params.construct(context)
 
@@ -409,6 +424,10 @@
         allowQueryAll.set(true)
 
         assertFails { runMethod(target, NON_VERIFIER_UID) }
+
+        allowDump.set(true)
+
+        runMethod(target, NON_VERIFIER_UID)
     }
 
     private fun approvedVerifier() {
@@ -794,8 +813,12 @@
             }
 
             val valueAsInt = value as? Int
-            if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
-                throw AssertionError("Expected call to return false, was $value")
+            if (valueAsInt != null) {
+                if (valueAsInt == DomainVerificationManager.STATUS_OK) {
+                    throw AssertionError("Expected call to return false, was $value")
+                }
+            } else {
+                throw AssertionError("Expected call to fail")
             }
         } catch (e: SecurityException) {
         } catch (e: PackageManager.NameNotFoundException) {
@@ -807,7 +830,7 @@
         // System/shell only
         INTERNAL,
 
-        // INTERNAL || non-legacy domain verification agent
+        // INTERNAL || non-legacy domain verification agent || DUMP permission
         QUERENT,
 
         // INTERNAL || domain verification agent
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index e472b06..d710308 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -1047,7 +1047,8 @@
         verifyLightStateConditions(LIGHT_STATE_IDLE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT),
-                longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX));
+                longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
+                eq(false));
 
         // Should just alternate between IDLE and IDLE_MAINTENANCE now.
 
@@ -1055,19 +1056,22 @@
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET),
-                longThat(l -> l == mConstants.FLEX_TIME_SHORT));
+                longThat(l -> l == mConstants.FLEX_TIME_SHORT),
+                eq(true));
 
         mDeviceIdleController.stepLightIdleStateLocked("testing");
         verifyLightStateConditions(LIGHT_STATE_IDLE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT),
-                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX));
+                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
+                eq(false));
 
         mDeviceIdleController.stepLightIdleStateLocked("testing");
         verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET),
-                longThat(l -> l == mConstants.FLEX_TIME_SHORT));
+                longThat(l -> l == mConstants.FLEX_TIME_SHORT),
+                eq(true));
 
         // Test that motion doesn't reset the idle timeout.
         mDeviceIdleController.handleMotionDetectedLocked(50, "test");
@@ -1076,7 +1080,8 @@
         verifyLightStateConditions(LIGHT_STATE_IDLE);
         inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked(
                 longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT),
-                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX));
+                longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX),
+                eq(false));
     }
 
     ///////////////// EXIT conditions ///////////////////
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java
new file mode 100644
index 0000000..8973a89
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceControllerTests.java
@@ -0,0 +1,455 @@
+/*
+ * 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.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+import android.service.games.GameService;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.SystemService;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+
+import java.util.List;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public final class GameServiceControllerTests {
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private Resources mMockResources;
+    @Mock
+    private Context mMockContext;
+    private MockitoSession mMockingSession;
+
+    private static UserInfo eligibleUserInfo(int uid) {
+        return new UserInfo(uid, "", "", UserInfo.FLAG_FULL);
+    }
+
+    private static UserInfo managedUserInfo(int uid) {
+        UserInfo userInfo = eligibleUserInfo(uid);
+        userInfo.userType = UserManager.USER_TYPE_PROFILE_MANAGED;
+        return userInfo;
+    }
+
+    private static ResolveInfo resolveInfo(ServiceInfo serviceInfo) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        return resolveInfo;
+    }
+
+    private static ServiceInfo serviceInfo(String packageName, String name, boolean isEnabled) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName;
+        applicationInfo.enabled = true;
+
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.applicationInfo = applicationInfo;
+        serviceInfo.packageName = packageName;
+        serviceInfo.name = name;
+        serviceInfo.enabled = isEnabled;
+        return serviceInfo;
+    }
+
+    private static SystemService.TargetUser managedTargetUser(int ineligibleUserId) {
+        return new SystemService.TargetUser(managedUserInfo(ineligibleUserId));
+    }
+
+    private static SystemService.TargetUser eligibleTargetUser(int userId) {
+        return new SystemService.TargetUser(eligibleUserInfo(userId));
+    }
+
+    private static UserHandle userWithId(int userId) {
+        return argThat(userInfo -> userInfo.getIdentifier() == userId);
+    }
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .startMocking();
+    }
+
+    @After
+    public void tearDown() {
+        mMockingSession.finishMocking();
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithNoUser() {
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.onBootComplete();
+
+        verifyNoServiceBound();
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithManagedUser() {
+        int userId = 12345;
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(managedTargetUser(userId));
+        gameServiceController.onBootComplete();
+
+        verifyNoServiceBound();
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithUserAndNoSystemGamesServiceSet() {
+        seedSystemGameServicePackageName("");
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(eligibleTargetUser(1000));
+        gameServiceController.onBootComplete();
+
+        verifyNoServiceBound();
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithUserAndSystemGamesServiceDoesNotExist() {
+        int userId = 12345;
+        String gameServicePackageName = "game.service.package";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of());
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
+        gameServiceController.onBootComplete();
+
+        verifyNoServiceBound();
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithUserAndSystemGamesServiceSet() {
+        int userId = 12345;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
+        gameServiceController.onBootComplete();
+
+        verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, gameServiceComponent);
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithUserAndSystemGamesServiceNotEnabled() {
+        int userId = 12345;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, false))));
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
+        gameServiceController.onBootComplete();
+
+        verifyNoServiceBound();
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithUserAndSystemGamesServiceHasMultipleComponents() {
+        int userId = 12345;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent1 = "game.service.package.example.GameService1";
+        String gameServiceComponent2 = "game.service.package.example.GameService2";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent1, true)),
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent2, true))));
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
+        gameServiceController.onBootComplete();
+
+        verifyServiceBoundForUserAndComponent(userId, gameServicePackageName,
+                gameServiceComponent1);
+    }
+
+    @Test
+    public void testStartConnectionOnBootWithUserAndSystemGamesServiceHasDisabledComponent() {
+        int userId = 12345;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent1 = "game.service.package.example.GameService1";
+        String gameServiceComponent2 = "game.service.package.example.GameService2";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent1, false)),
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent2, true))));
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
+        gameServiceController.onBootComplete();
+
+        verifyServiceBoundForUserAndComponent(userId, gameServicePackageName,
+                gameServiceComponent2);
+    }
+
+    @Test
+    public void testSwitchFromEligibleUserToEligibleUser() {
+        int userId1 = 1;
+        int userId2 = 2;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceResolveInfos(gameServicePackageName, userId2, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceToBindSuccessfully();
+
+        GameServiceController gameServiceController = new GameServiceController(mMockContext);
+
+        gameServiceController.onBootComplete();
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId1));
+
+        verifyServiceBoundForUserAndComponent(userId1, gameServicePackageName,
+                gameServiceComponent);
+
+        gameServiceController.notifyNewForegroundUser(eligibleTargetUser(userId2));
+
+        verify(mMockContext).unbindService(any());
+        verifyServiceBoundForUserAndComponent(userId2, gameServicePackageName,
+                gameServiceComponent);
+    }
+
+    @Test
+    public void testSwitchFromEligibleUserToIneligibleUser() {
+        int eligibleUserId = 1;
+        int ineligibleUserId = 2;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, eligibleUserId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceToBindSuccessfully();
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.onBootComplete();
+        gameServiceController.notifyUserStarted(eligibleTargetUser(eligibleUserId));
+
+        verifyServiceBoundForUserAndComponent(eligibleUserId, gameServicePackageName,
+                gameServiceComponent);
+
+        gameServiceController.notifyNewForegroundUser(managedTargetUser(ineligibleUserId));
+
+        verify(mMockContext).unbindService(any());
+    }
+
+    @Test
+    public void testSwitchFromIneligibleUserToEligibleUser() {
+        int eligibleUserId = 1;
+        int ineligibleUserId = 2;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, eligibleUserId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceToBindSuccessfully();
+
+        GameServiceController gameServiceController = new GameServiceController(mMockContext);
+
+        gameServiceController.onBootComplete();
+        gameServiceController.notifyUserStarted(managedTargetUser(ineligibleUserId));
+
+        verifyNoServiceBound();
+
+        gameServiceController.notifyNewForegroundUser(eligibleTargetUser(eligibleUserId));
+
+        verifyServiceBoundForUserAndComponent(eligibleUserId, gameServicePackageName,
+                gameServiceComponent);
+    }
+
+    @Test
+    public void testMultipleRunningUsers() {
+        int userId1 = 123;
+        int userId2 = 456;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceToBindSuccessfully();
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.onBootComplete();
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId1));
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId2));
+
+        verifyServiceBoundForUserAndComponent(userId1, gameServicePackageName,
+                gameServiceComponent);
+        verifyServiceNotBoundForUser(userId2);
+        verify(mMockContext, never()).unbindService(any());
+    }
+
+    @Test
+    public void testForegroundUserStopped() {
+        int userId = 123123;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceToBindSuccessfully();
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+
+        gameServiceController.onBootComplete();
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId));
+
+        verifyServiceBoundForUserAndComponent(userId, gameServicePackageName, gameServiceComponent);
+
+        gameServiceController.notifyUserStopped(eligibleTargetUser(userId));
+
+        verify(mMockContext).unbindService(any());
+    }
+
+    @Test
+    public void testNonForegroundUserStopped() {
+        int userId1 = 123;
+        int userId2 = 456;
+        String gameServicePackageName = "game.service.package";
+        String gameServiceComponent = "game.service.package.example.GameService";
+        seedSystemGameServicePackageName(gameServicePackageName);
+        seedGameServiceResolveInfos(gameServicePackageName, userId1, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceResolveInfos(gameServicePackageName, userId2, ImmutableList.of(
+                resolveInfo(serviceInfo(gameServicePackageName, gameServiceComponent, true))));
+        seedGameServiceToBindSuccessfully();
+
+        GameServiceController gameServiceController =
+                new GameServiceController(mMockContext);
+        InOrder inOrder = Mockito.inOrder(mMockContext);
+
+        gameServiceController.onBootComplete();
+        gameServiceController.notifyUserStarted(eligibleTargetUser(userId1));
+
+        inOrder.verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), userWithId(userId1));
+
+        gameServiceController.notifyNewForegroundUser(eligibleTargetUser(userId2));
+
+        inOrder.verify(mMockContext).unbindService(any());
+        inOrder.verify(mMockContext).bindServiceAsUser(any(), any(), anyInt(), userWithId(userId2));
+
+        gameServiceController.notifyUserStopped(eligibleTargetUser(userId1));
+
+        inOrder.verify(mMockContext, never()).unbindService(any());
+    }
+
+    private void seedSystemGameServicePackageName(String gameServicePackageName) {
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getString(com.android.internal.R.string.config_systemGameService))
+                .thenReturn(gameServicePackageName);
+    }
+
+    private void seedGameServiceResolveInfos(String gameServicePackageName, int userId,
+            List<ResolveInfo> resolveInfos) {
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        doReturn(resolveInfos)
+                .when(mMockPackageManager).queryIntentServicesAsUser(
+                argThat(intent ->
+                        intent != null
+                                && intent.getAction().equals(GameService.SERVICE_INTERFACE)
+                                && intent.getPackage().equals(gameServicePackageName)
+                ),
+                eq(PackageManager.MATCH_SYSTEM_ONLY),
+                eq(userId));
+    }
+
+    private void seedGameServiceToBindSuccessfully() {
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+    }
+
+    private void verifyNoServiceBound() {
+        verify(mMockContext, never()).bindServiceAsUser(any(), any(), anyInt(), any());
+    }
+
+    private void verifyServiceBoundForUserAndComponent(int userId, String gameServicePackageName,
+            String gameServiceComponent) {
+        verify(mMockContext).bindServiceAsUser(
+                argThat(intent -> intent.getAction().equals(GameService.SERVICE_INTERFACE)
+                        && intent.getComponent().getPackageName().equals(gameServicePackageName)
+                        && intent.getComponent().getClassName().equals(gameServiceComponent)),
+                any(),
+                anyInt(), argThat(userInfo -> userInfo.getIdentifier() == userId));
+    }
+
+    private void verifyServiceNotBoundForUser(int userId) {
+        verify(mMockContext, never()).bindServiceAsUser(
+                any(),
+                any(),
+                anyInt(), userWithId(userId));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
index 17d7c51..d6db1b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/communal/CommunalManagerServiceTest.java
@@ -123,6 +123,8 @@
 
         doNothing().when(mContextSpy).enforceCallingPermission(
                 eq(Manifest.permission.WRITE_COMMUNAL_STATE), anyString());
+        doNothing().when(mContextSpy).enforceCallingPermission(
+                eq(Manifest.permission.READ_COMMUNAL_STATE), anyString());
 
         mService = new CommunalManagerService(mContextSpy);
         mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
@@ -203,6 +205,18 @@
     }
 
     @Test
+    public void testIsCommunalMode_isTrue() throws RemoteException {
+        mBinder.setCommunalViewShowing(true);
+        assertThat(mBinder.isCommunalMode()).isTrue();
+    }
+
+    @Test
+    public void testIsCommunalMode_isFalse() throws RemoteException {
+        mBinder.setCommunalViewShowing(false);
+        assertThat(mBinder.isCommunalMode()).isFalse();
+    }
+
+    @Test
     public void testIntercept_unlocked_communalOff_appNotEnabled_showWhenLockedOff() {
         when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
         mAInfo.flags = 0;
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java
index d728451..189396f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeAppOpsHelper.java
@@ -31,7 +31,7 @@
     private static class AppOp {
         AppOp() {}
         boolean mAllowed = true;
-        boolean mStarted = false;
+        int mStarted = 0;
         int mNoteCount = 0;
     }
 
@@ -49,7 +49,7 @@
 
     public boolean isAppOpStarted(int appOp, String packageName) {
         AppOp myAppOp = getOp(packageName, appOp);
-        return myAppOp.mStarted;
+        return myAppOp.mStarted > 0;
     }
 
     public int getAppOpNoteCount(int appOp, String packageName) {
@@ -63,16 +63,15 @@
         if (!myAppOp.mAllowed) {
             return false;
         }
-        Preconditions.checkState(!myAppOp.mStarted);
-        myAppOp.mStarted = true;
+        myAppOp.mStarted++;
         return true;
     }
 
     @Override
     public void finishOp(int appOp, CallerIdentity callerIdentity) {
         AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp);
-        Preconditions.checkState(myAppOp.mStarted);
-        myAppOp.mStarted = false;
+        Preconditions.checkState(myAppOp.mStarted > 0);
+        myAppOp.mStarted--;
     }
 
     @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
deleted file mode 100644
index 94dcdf9..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java
+++ /dev/null
@@ -1,143 +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.location.injector;
-
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.mockito.MockitoAnnotations.initMocks;
-
-import android.location.util.identity.CallerIdentity;
-import android.platform.test.annotations.Presubmit;
-
-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;
-
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LocationAttributionHelperTest {
-
-    @Mock private AppOpsHelper mAppOpsHelper;
-
-    private LocationAttributionHelper mHelper;
-
-    @Before
-    public void setUp() {
-        initMocks(this);
-
-        when(mAppOpsHelper.startOpNoThrow(anyInt(), any(CallerIdentity.class))).thenReturn(true);
-
-        mHelper = new LocationAttributionHelper(mAppOpsHelper);
-    }
-
-    @Test
-    public void testLocationMonitoring() {
-        CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null);
-        CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null);
-
-        mHelper.reportLocationStart(caller1);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-
-        mHelper.reportLocationStart(caller1);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-
-        mHelper.reportLocationStart(caller2);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-
-        mHelper.reportLocationStart(caller2);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-
-        mHelper.reportLocationStop(caller1);
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-        mHelper.reportLocationStop(caller1);
-        verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller1));
-
-        mHelper.reportLocationStop(caller2);
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-        mHelper.reportLocationStop(caller2);
-        verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller2));
-    }
-
-    @Test
-    public void testHighPowerLocationMonitoring() {
-        CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null);
-        CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null);
-
-        mHelper.reportHighPowerLocationStart(caller1);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-
-        mHelper.reportHighPowerLocationStart(caller1);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-
-        mHelper.reportHighPowerLocationStart(caller2);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-
-        mHelper.reportHighPowerLocationStart(caller2);
-        verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-
-        mHelper.reportHighPowerLocationStop(caller1);
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-        mHelper.reportHighPowerLocationStop(caller1);
-        verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller1));
-
-        mHelper.reportHighPowerLocationStop(caller2);
-        verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-        mHelper.reportHighPowerLocationStop(caller2);
-        verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION,
-                CallerIdentity.forAggregation(caller2));
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index bd24cfd..02cacb7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -33,7 +33,6 @@
     private final FakeScreenInteractiveHelper mScreenInteractiveHelper;
     private final FakeDeviceStationaryHelper mDeviceStationaryHelper;
     private final FakeDeviceIdleHelper mDeviceIdleHelper;
-    private final LocationAttributionHelper mLocationAttributionHelper;
     private final FakeEmergencyHelper mEmergencyHelper;
     private final LocationUsageLogger mLocationUsageLogger;
 
@@ -49,7 +48,6 @@
         mScreenInteractiveHelper = new FakeScreenInteractiveHelper();
         mDeviceStationaryHelper = new FakeDeviceStationaryHelper();
         mDeviceIdleHelper = new FakeDeviceIdleHelper();
-        mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper);
         mEmergencyHelper = new FakeEmergencyHelper();
         mLocationUsageLogger = new LocationUsageLogger();
     }
@@ -110,11 +108,6 @@
     }
 
     @Override
-    public LocationAttributionHelper getLocationAttributionHelper() {
-        return mLocationAttributionHelper;
-    }
-
-    @Override
     public EmergencyHelper getEmergencyHelper() {
         return mEmergencyHelper;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 4d6f49e..4eba219 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,6 +90,19 @@
     }
 
     @Test
+    public void testThrottle_lowInterval() {
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(0).build();
+
+        mProvider.getController().setRequest(request);
+        mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+        verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+        mInjector.getDeviceStationaryHelper().setStationary(true);
+        mInjector.getDeviceIdleHelper().setIdle(true);
+        verify(mListener, after(1500).times(2)).onReportLocation(any(LocationResult.class));
+    }
+
+    @Test
     public void testThrottle_stationaryExit() {
         ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
 
@@ -104,17 +117,16 @@
 
         mInjector.getDeviceIdleHelper().setIdle(true);
         verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
-        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
-        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
 
         mInjector.getDeviceStationaryHelper().setStationary(false);
         verify(mDelegate, times(2)).onSetRequest(request);
-        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
     }
 
     @Test
     public void testThrottle_idleExit() {
-        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build();
 
         mProvider.getController().setRequest(request);
         verify(mDelegate).onSetRequest(request);
@@ -127,17 +139,16 @@
 
         mInjector.getDeviceStationaryHelper().setStationary(true);
         verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
-        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
-        verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
 
         mInjector.getDeviceIdleHelper().setIdle(false);
         verify(mDelegate, times(2)).onSetRequest(request);
-        verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+        verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
     }
 
     @Test
     public void testThrottle_NoInitialLocation() {
-        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+        ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(1000).build();
 
         mProvider.getController().setRequest(request);
         verify(mDelegate).onSetRequest(request);
@@ -149,11 +160,11 @@
         mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
         verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
         verify(mDelegate, times(1)).onSetRequest(ProviderRequest.EMPTY_REQUEST);
-        verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, timeout(1100).times(2)).onReportLocation(any(LocationResult.class));
 
         mInjector.getDeviceStationaryHelper().setStationary(false);
         verify(mDelegate, times(2)).onSetRequest(request);
-        verify(mListener, after(75).times(2)).onReportLocation(any(LocationResult.class));
+        verify(mListener, after(1000).times(2)).onReportLocation(any(LocationResult.class));
     }
 
     @Test
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e3c60fd..c3a364e 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -32,6 +32,7 @@
         "services.appwidget",
         "services.autofill",
         "services.backup",
+        "services.companion",
         "services.core",
         "services.devicepolicy",
         "services.net",
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 2eb9e34..a0d86c9 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -187,6 +187,30 @@
     }
 
     @Test
+    public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_enabled() {
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(4000);
+        assertEquals(4000,
+                mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext,
+                        FAKE_USER_ID));
+    }
+
+    @Test
+    public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_disabled() {
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(0);
+        assertEquals(0,
+                mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext,
+                        FAKE_USER_ID));
+    }
+
+    @Test
+    public void testGetEmergencyGesturePowerButtonCooldownPeriodMs_cappedAtMaximum() {
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(10000);
+        assertEquals(GestureLauncherService.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX,
+                mGestureLauncherService.getEmergencyGesturePowerButtonCooldownPeriodMs(mContext,
+                        FAKE_USER_ID));
+    }
+
+    @Test
     public void testHandleCameraLaunchGesture_userSetupComplete() {
         withUserSetupCompleteValue(true);
 
@@ -645,6 +669,211 @@
     }
 
     @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_singleTaps_cooldownTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent single tap is intercepted, but should not trigger any gesture
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(intercepted);
+        assertFalse(outLaunched.value);
+
+        // Add enough interval to reset consecutive tap count
+        interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Another single tap should be the same (intercepted but should not trigger gesture)
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        interactive = true;
+        outLaunched = new MutableBoolean(true);
+        intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
+    public void
+    testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() {
+        // Enable camera double tap gesture
+        withCameraDoubleTapPowerEnableConfigValue(true);
+        withCameraDoubleTapPowerDisableSettingValue(0);
+        mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent double tap is intercepted, but should not trigger any gesture
+        for (int i = 0; i < 2; i++) {
+            KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION,
+                    IGNORED_CODE, IGNORED_REPEAT);
+            boolean interactive = true;
+            MutableBoolean outLaunched = new MutableBoolean(true);
+            boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent,
+                    interactive, outLaunched);
+            assertTrue(intercepted);
+            assertFalse(outLaunched.value);
+            interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            eventTime += interval;
+        }
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_fiveTaps_cooldownTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent 5 taps are intercepted, but should not trigger any gesture
+        for (int i = 0; i < 5; i++) {
+            KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION,
+                    IGNORED_CODE, IGNORED_REPEAT);
+            boolean interactive = true;
+            MutableBoolean outLaunched = new MutableBoolean(true);
+            boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent,
+                    interactive, outLaunched);
+            assertTrue(intercepted);
+            assertFalse(outLaunched.value);
+            interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            eventTime += interval;
+        }
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_longPress_cooldownTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent long press is intercepted, but should not trigger any gesture
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
+                KeyEvent.FLAG_LONG_PRESS);
+
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
+    public void testInterceptPowerKeyDown_triggerEmergency_cooldownDisabled_cooldownNotTriggered() {
+        // Disable power button cooldown by setting cooldown period to 0
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(0);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to reset consecutive tap count
+        long interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Subsequent single tap is NOT intercepted
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+
+        // Add enough interval to reset consecutive tap count
+        interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Long press also NOT intercepted
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
+                KeyEvent.FLAG_LONG_PRESS);
+        interactive = true;
+        outLaunched = new MutableBoolean(true);
+        intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
+    public void
+    testInterceptPowerKeyDown_triggerEmergency_outsideCooldownPeriod_cooldownNotTriggered() {
+        // Enable power button cooldown
+        withEmergencyGesturePowerButtonCooldownPeriodMsValue(5000);
+        mGestureLauncherService.updateEmergencyGesturePowerButtonCooldownPeriodMs();
+
+        // Trigger emergency by tapping button 5 times
+        long eventTime = triggerEmergencyGesture();
+
+        // Add enough interval to be outside of cooldown period
+        long interval = 5001;
+        eventTime += interval;
+
+        // Subsequent single tap is NOT intercepted
+        KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        boolean interactive = true;
+        MutableBoolean outLaunched = new MutableBoolean(true);
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+
+        // Add enough interval to reset consecutive tap count
+        interval = GestureLauncherService.POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS + 1;
+        eventTime += interval;
+
+        // Long press also NOT intercepted
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT, IGNORED_META_STATE, IGNORED_DEVICE_ID, IGNORED_SCANCODE,
+                KeyEvent.FLAG_LONG_PRESS);
+        interactive = true;
+        outLaunched = new MutableBoolean(true);
+        intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertFalse(intercepted);
+        assertFalse(outLaunched.value);
+    }
+
+    @Test
     public void testInterceptPowerKeyDown_longpress() {
         withCameraDoubleTapPowerEnableConfigValue(true);
         withCameraDoubleTapPowerDisableSettingValue(0);
@@ -1153,6 +1382,45 @@
         assertEquals(1, tapCounts.get(1).intValue());
     }
 
+    /**
+     * Helper method to trigger emergency gesture by pressing button for 5 times.
+     * @return last event time.
+     */
+    private long triggerEmergencyGesture() {
+        // Enable emergency power gesture
+        withEmergencyGestureEnabledConfigValue(true);
+        withEmergencyGestureEnabledSettingValue(true);
+        mGestureLauncherService.updateEmergencyGestureEnabled();
+        withUserSetupCompleteValue(true);
+
+        // 4 button presses
+        long eventTime = INITIAL_EVENT_TIME_MILLIS;
+        boolean interactive = true;
+        KeyEvent keyEvent;
+        MutableBoolean outLaunched = new MutableBoolean(false);
+        for (int i = 0; i < 4; i++) {
+            keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                    IGNORED_REPEAT);
+            mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive, outLaunched);
+            final long interval = GestureLauncherService.CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
+            eventTime += interval;
+        }
+
+        // 5th button press should trigger the emergency flow
+        keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
+                IGNORED_REPEAT);
+        outLaunched.value = false;
+        boolean intercepted = mGestureLauncherService.interceptPowerKeyDown(keyEvent, interactive,
+                outLaunched);
+        assertTrue(outLaunched.value);
+        assertTrue(intercepted);
+        verify(mUiEventLogger, times(1))
+                .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
+        verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
+
+        return eventTime;
+    }
+
     private void withCameraDoubleTapPowerEnableConfigValue(boolean enableConfigValue) {
         when(mResources.getBoolean(
                 com.android.internal.R.bool.config_cameraDoubleTapPowerGestureEnabled))
@@ -1181,6 +1449,14 @@
                 UserHandle.USER_CURRENT);
     }
 
+    private void withEmergencyGesturePowerButtonCooldownPeriodMsValue(int period) {
+        Settings.Secure.putIntForUser(
+                mContentResolver,
+                Settings.Secure.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS,
+                period,
+                UserHandle.USER_CURRENT);
+    }
+
     private void withUserSetupCompleteValue(boolean userSetupComplete) {
         int userSetupCompleteValue = userSetupComplete ? 1 : 0;
         Settings.Secure.putIntForUser(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index a83d51b..d18030f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -50,6 +50,7 @@
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -65,6 +66,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityTrace;
 import android.accessibilityservice.IAccessibilityServiceClient;
@@ -584,8 +586,8 @@
         doAnswer((invocation) -> {
             ((Region) invocation.getArguments()[1]).set(region);
             return null;
-        }).when(mMockMagnificationProcessor).getMagnificationRegion(eq(displayId),
-                any(), anyBoolean());
+        }).when(mMockMagnificationProcessor).getFullscreenMagnificationRegion(eq(displayId), any(),
+                anyBoolean());
         when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
 
         final Region result = mServiceConnection.getMagnificationRegion(displayId);
@@ -619,7 +621,8 @@
     @Test
     public void resetMagnification() {
         final int displayId = 1;
-        when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+        when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+                true);
 
         final boolean result = mServiceConnection.resetMagnification(displayId, true);
         assertThat(result, is(true));
@@ -628,7 +631,8 @@
     @Test
     public void resetMagnification_cantControlMagnification_returnFalse() {
         final int displayId = 1;
-        when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+        when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+                true);
         when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
 
         final boolean result = mServiceConnection.resetMagnification(displayId, true);
@@ -638,7 +642,8 @@
     @Test
     public void resetMagnification_serviceNotBelongCurrentUser_returnFalse() {
         final int displayId = 1;
-        when(mMockMagnificationProcessor.reset(displayId, true)).thenReturn(true);
+        when(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn(
+                true);
         when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
 
         final boolean result = mServiceConnection.resetMagnification(displayId, true);
@@ -646,7 +651,7 @@
     }
 
     @Test
-    public void setMagnificationScaleAndCenter_cantControlMagnification_returnFalse() {
+    public void setMagnificationConfig_cantControlMagnification_returnFalse() {
         final int displayId = 1;
         final float scale = 1.8f;
         final float centerX = 50.5f;
@@ -659,13 +664,12 @@
                 SERVICE_ID)).thenReturn(true);
         when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
 
-        final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
-                displayId, scale, centerX, centerY, true);
-        assertThat(result, is(false));
+        final boolean result = mServiceConnection.setMagnificationConfig(displayId, config, true);
+        assertFalse(result);
     }
 
     @Test
-    public void setMagnificationScaleAndCenter_serviceNotBelongCurrentUser_returnFalse() {
+    public void setMagnificationConfig_serviceNotBelongCurrentUser_returnFalse() {
         final int displayId = 1;
         final float scale = 1.8f;
         final float centerX = 50.5f;
@@ -678,9 +682,8 @@
                 SERVICE_ID)).thenReturn(true);
         when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
 
-        final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
-                displayId, scale, centerX, centerY, true);
-        assertThat(result, is(false));
+        final boolean result = mServiceConnection.setMagnificationConfig(displayId, config, true);
+        assertFalse(result);
     }
 
     @Test (expected = SecurityException.class)
@@ -840,6 +843,11 @@
         }
 
         @Override
+        public int setInputMethodEnabled(String imeId, boolean enabled) throws RemoteException {
+            return AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN;
+        }
+
+        @Override
         public boolean isAccessibilityButtonAvailable() throws RemoteException {
             return false;
         }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
index 74dd291..621507e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationProcessorTest.java
@@ -98,7 +98,7 @@
                 .setScale(TEST_SCALE).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        float scale = mMagnificationProcessor.getScale(TEST_DISPLAY);
+        float scale = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getScale();
 
         assertEquals(scale, TEST_SCALE, 0);
     }
@@ -111,20 +111,19 @@
         setMagnificationActivated(TEST_DISPLAY, config);
 
         float centerX = mMagnificationProcessor.getCenterX(
-                TEST_DISPLAY,  /* canControlMagnification= */true);
+                TEST_DISPLAY, /* canControlMagnification= */true);
 
         assertEquals(centerX, TEST_CENTER_X, 0);
     }
 
     @Test
-    public void getCenterX_canControlWindowMagnification_returnCenterX() {
+    public void getCenterX_controlWindowMagnification_returnCenterX() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
                 .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setCenterX(TEST_CENTER_X).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        float centerX = mMagnificationProcessor.getCenterX(
-                TEST_DISPLAY,  /* canControlMagnification= */true);
+        float centerX = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterX();
 
         assertEquals(centerX, TEST_CENTER_X, 0);
     }
@@ -143,14 +142,13 @@
     }
 
     @Test
-    public void getCenterY_canControlWindowMagnification_returnCenterY() {
+    public void getCenterY_controlWindowMagnification_returnCenterY() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
                 .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        float centerY = mMagnificationProcessor.getCenterY(
-                TEST_DISPLAY,  /* canControlMagnification= */false);
+        float centerY = mMagnificationProcessor.getMagnificationConfig(TEST_DISPLAY).getCenterY();
 
         assertEquals(centerY, TEST_CENTER_Y, 0);
     }
@@ -159,7 +157,7 @@
     public void getMagnificationRegion_canControlFullscreenMagnification_returnRegion() {
         final Region region = new Region(10, 20, 100, 200);
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
-        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+        mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY,
                 region,  /* canControlMagnification= */true);
 
         verify(mMockFullScreenMagnificationController).getMagnificationRegion(eq(TEST_DISPLAY),
@@ -167,17 +165,6 @@
     }
 
     @Test
-    public void getMagnificationRegion_canControlWindowMagnification_returnRegion() {
-        final Region region = new Region(10, 20, 100, 200);
-        setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
-        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
-                region,  /* canControlMagnification= */true);
-
-        verify(mMockWindowMagnificationManager).getMagnificationSourceBounds(eq(TEST_DISPLAY),
-                eq(region));
-    }
-
-    @Test
     public void getMagnificationRegion_fullscreenModeNotRegistered_shouldRegisterThenUnregister() {
         final Region region = new Region(10, 20, 100, 200);
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
@@ -188,7 +175,7 @@
                 any());
 
         final Region result = new Region();
-        mMagnificationProcessor.getMagnificationRegion(TEST_DISPLAY,
+        mMagnificationProcessor.getFullscreenMagnificationRegion(TEST_DISPLAY,
                 result, /* canControlMagnification= */true);
         assertEquals(region, result);
         verify(mMockFullScreenMagnificationController).register(TEST_DISPLAY);
@@ -237,7 +224,7 @@
     public void reset_fullscreenMagnificationActivated() {
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_FULLSCREEN);
 
-        mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+        mMagnificationProcessor.resetFullscreenMagnification(TEST_DISPLAY, /* animate= */false);
 
         verify(mMockFullScreenMagnificationController).reset(TEST_DISPLAY, false);
     }
@@ -246,7 +233,7 @@
     public void reset_windowMagnificationActivated() {
         setMagnificationActivated(TEST_DISPLAY, MAGNIFICATION_MODE_WINDOW);
 
-        mMagnificationProcessor.reset(TEST_DISPLAY, /* animate= */false);
+        mMagnificationProcessor.resetCurrentMagnification(TEST_DISPLAY, /* animate= */false);
 
         verify(mMockWindowMagnificationManager).reset(TEST_DISPLAY);
     }
@@ -282,16 +269,13 @@
     }
 
     @Test
-    public void setMagnificationConfig_fullscreenEnabled_expectedConfigValues() {
+    public void getMagnificationConfig_fullscreenEnabled_expectedConfigValues() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
                 .setMode(MAGNIFICATION_MODE_FULLSCREEN)
                 .setScale(TEST_SCALE)
                 .setCenterX(TEST_CENTER_X)
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
-     //   mMockFullScreenMagnificationController.unregister(TEST_DISPLAY);
-        mMagnificationProcessor.setMagnificationConfig(
-                TEST_DISPLAY, config, true, SERVICE_ID);
 
         final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
                 TEST_DISPLAY);
@@ -300,7 +284,7 @@
     }
 
     @Test
-    public void setMagnificationConfig_windowEnabled_expectedConfigValues() {
+    public void getMagnificationConfig_windowEnabled_expectedConfigValues() {
         final MagnificationConfig config = new MagnificationConfig.Builder()
                 .setMode(MAGNIFICATION_MODE_WINDOW)
                 .setScale(TEST_SCALE)
@@ -308,9 +292,6 @@
                 .setCenterY(TEST_CENTER_Y).build();
         setMagnificationActivated(TEST_DISPLAY, config);
 
-        mMagnificationProcessor.setMagnificationConfig(
-                TEST_DISPLAY, config, true, SERVICE_ID);
-
         final MagnificationConfig result = mMagnificationProcessor.getMagnificationConfig(
                 TEST_DISPLAY);
 
@@ -455,6 +436,9 @@
             doAnswer(enableWindowMagnificationStubAnswer).when(
                     mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
                     anyFloat(), anyFloat(), anyFloat());
+            doAnswer(enableWindowMagnificationStubAnswer).when(
+                    mWindowMagnificationManager).enableWindowMagnification(eq(TEST_DISPLAY),
+                    anyFloat(), anyFloat(), anyFloat(), any());
         }
 
         public void resetAndStubMethods() {
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 8b6b7c2..1d6ed03 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -296,14 +296,6 @@
     }
 
     @Test
-    public void testToggleSplitScreen_legacy() {
-        setupWithRealContext();
-        mSystemActionPerformer.performSystemAction(
-                AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN);
-        verify(mMockStatusBarManagerInternal).toggleSplitScreen();
-    }
-
-    @Test
     public void testScreenshot_requestsFromScreenshotHelper_legacy() {
         setupWithMockContext();
         mSystemActionPerformer.performSystemAction(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index ec1a0c2..08421de 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -55,8 +55,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -79,6 +81,7 @@
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     private static final int TEST_SERVICE_ID = 1;
     private static final Region MAGNIFICATION_REGION = new Region(0, 0, 500, 600);
+    private static final Rect TEST_RECT = new Rect(0, 50, 100, 51);
     private static final float MAGNIFIED_CENTER_X = 100;
     private static final float MAGNIFIED_CENTER_Y = 200;
     private static final float DEFAULT_SCALE = 3f;
@@ -108,6 +111,11 @@
     private MagnificationController mMagnificationController;
     private FullScreenMagnificationControllerStubber mScreenMagnificationControllerStubber;
 
+    @Mock
+    private WindowManagerInternal mMockWindowManagerInternal;
+    @Mock
+    private WindowManagerInternal.AccessibilityControllerInternal mMockA11yController;
+
     // To mock package-private class
     @Rule
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
@@ -117,6 +125,12 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         FakeSettingsProvider.clearSettingsProvider();
+
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerInternal);
+        when(mMockWindowManagerInternal.getAccessibilityController()).thenReturn(
+                mMockA11yController);
+
         mMockResolver = new MockContentResolver();
         mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContext.getContentResolver()).thenReturn(mMockResolver);
@@ -132,8 +146,8 @@
         mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
         mScreenMagnificationControllerStubber = new FullScreenMagnificationControllerStubber(
                 mScreenMagnificationController);
-        mMagnificationController = spy(new MagnificationController(mService, new Object(), mContext,
-                mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider));
+        mMagnificationController = new MagnificationController(mService, new Object(), mContext,
+                mScreenMagnificationController, mWindowMagnificationManager, mScaleProvider);
 
         mMagnificationController.setMagnificationCapabilities(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
@@ -439,16 +453,65 @@
 
     @Test
     public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() {
-        mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, false);
+        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, false);
 
-        verify(mMagnificationController).logMagnificationUsageState(
+        verify(spyController).logMagnificationUsageState(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong());
     }
 
     @Test
-    public void onWinodwModeActivated_fullScreenIsActivatedByExternal_fullScreenIsDisabled() {
+    public void onRectangleOnScreenRequested_fullScreenIsActivated_fullScreenDispatchEvent() {
+        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY,
+                true);
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
+                callbacks = getUiChangesForAccessibilityCallbacks();
+
+        callbacks.onRectangleOnScreenRequested(TEST_DISPLAY,
+                TEST_RECT.left, TEST_RECT.top, TEST_RECT.right, TEST_RECT.bottom);
+
+        verify(mScreenMagnificationController).onRectangleOnScreenRequested(eq(TEST_DISPLAY),
+                eq(TEST_RECT.left), eq(TEST_RECT.top), eq(TEST_RECT.right), eq(TEST_RECT.bottom));
+        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_fullScreenIsInactivated_noneDispatchEvent() {
+        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY,
+                true);
+        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY,
+                false);
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
+                callbacks = getUiChangesForAccessibilityCallbacks();
+
+        callbacks.onRectangleOnScreenRequested(TEST_DISPLAY,
+                TEST_RECT.left, TEST_RECT.top, TEST_RECT.right, TEST_RECT.bottom);
+
+        verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyInt());
+        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void onRectangleOnScreenRequested_NoneIsActivated_noneDispatchEvent() {
+        WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
+                callbacks = getUiChangesForAccessibilityCallbacks();
+
+        callbacks.onRectangleOnScreenRequested(TEST_DISPLAY,
+                TEST_RECT.left, TEST_RECT.top, TEST_RECT.right, TEST_RECT.bottom);
+
+        verify(mScreenMagnificationController, never()).onRectangleOnScreenRequested(
+                eq(TEST_DISPLAY), anyInt(), anyInt(), anyInt(), anyInt());
+        verify(mWindowMagnificationManager, never()).onRectangleOnScreenRequested(anyInt(),
+                anyInt(), anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void onWindowModeActivated_fullScreenIsActivatedByExternal_fullScreenIsDisabled() {
         mScreenMagnificationController.setScaleAndCenter(TEST_DISPLAY,
                 DEFAULT_SCALE, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y,
                 true, TEST_SERVICE_ID);
@@ -461,11 +524,12 @@
     @Test
     public void
             onFullScreenMagnificationActivationState_fullScreenActivated_logFullScreenDuration() {
-        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
 
-        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false);
+        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, false);
 
-        verify(mMagnificationController).logMagnificationUsageState(
+        verify(spyController).logMagnificationUsageState(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong());
     }
 
@@ -627,43 +691,48 @@
 
     @Test
     public void imeWindowStateShown_windowMagnifying_logWindowMode() {
-        mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        mMagnificationController.onImeWindowVisibilityChanged(true);
+        spyController.onImeWindowVisibilityChanged(true);
 
-        verify(mMagnificationController).logMagnificationModeWithIme(
+        verify(spyController).logMagnificationModeWithIme(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
     }
 
     @Test
     public void imeWindowStateShown_fullScreenMagnifying_logFullScreenMode() {
-        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
 
-        mMagnificationController.onImeWindowVisibilityChanged(true);
+        spyController.onImeWindowVisibilityChanged(true);
 
-        verify(mMagnificationController).logMagnificationModeWithIme(
+        verify(spyController).logMagnificationModeWithIme(
                 eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
     }
 
     @Test
     public void imeWindowStateShown_noMagnifying_noLogAnyMode() {
-        mMagnificationController.onImeWindowVisibilityChanged(true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onImeWindowVisibilityChanged(true);
 
-        verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
+        verify(spyController, never()).logMagnificationModeWithIme(anyInt());
     }
 
     @Test
     public void imeWindowStateHidden_windowMagnifying_noLogAnyMode() {
-        mMagnificationController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onFullScreenMagnificationActivationState(TEST_DISPLAY, true);
 
-        verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
+        verify(spyController, never()).logMagnificationModeWithIme(anyInt());
     }
 
     @Test
     public void imeWindowStateHidden_fullScreenMagnifying_noLogAnyMode() {
-        mMagnificationController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
+        MagnificationController spyController = spy(mMagnificationController);
+        spyController.onWindowMagnificationActivationState(TEST_DISPLAY, true);
 
-        verify(mMagnificationController, never()).logMagnificationModeWithIme(anyInt());
+        verify(spyController, never()).logMagnificationModeWithIme(anyInt());
     }
 
     @Test
@@ -718,6 +787,17 @@
                 MAGNIFIED_CENTER_X).setCenterY(MAGNIFIED_CENTER_Y).build();
     }
 
+    private WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks
+            getUiChangesForAccessibilityCallbacks() {
+        ArgumentCaptor<WindowManagerInternal.AccessibilityControllerInternal
+                .UiChangesForAccessibilityCallbacks> captor = ArgumentCaptor.forClass(
+                WindowManagerInternal.AccessibilityControllerInternal
+                        .UiChangesForAccessibilityCallbacks.class);
+        verify(mMockWindowManagerInternal.getAccessibilityController())
+                .setUiChangesForAccessibilityCallbacks(captor.capture());
+        return captor.getValue();
+    }
+
     /**
      * Stubs public methods to simulate the real beahviours.
      */
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 aa7d6aa..bccd8a0b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -19,8 +19,12 @@
 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.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent;
@@ -31,22 +35,27 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import com.android.internal.backup.IBackupTransport;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.backup.internal.LifecycleOperationStorage;
 import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.params.BackupParams;
+import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
 
+import com.google.common.collect.ImmutableSet;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.IntConsumer;
+
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class UserBackupManagerServiceTest {
@@ -57,11 +66,10 @@
     @Mock IBackupManagerMonitor mBackupManagerMonitor;
     @Mock IBackupObserver mBackupObserver;
     @Mock PackageManager mPackageManager;
-    @Mock
-    TransportConnection mTransportConnection;
-    @Mock IBackupTransport mBackupTransport;
+    @Mock TransportConnection mTransportConnection;
+    @Mock BackupTransportClient mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
-
+    @Mock LifecycleOperationStorage mOperationStorage;
 
     private TestBackupService mService;
 
@@ -69,7 +77,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mService = new TestBackupService(mContext, mPackageManager);
+        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage);
         mService.setEnabled(true);
         mService.setSetupComplete(true);
     }
@@ -163,6 +171,31 @@
         assertThat(operationType).isEqualTo(OperationType.MIGRATION);
     }
 
+    @Test
+    public void testAgentDisconnected_cancelsCurrentOperations() throws Exception {
+        when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
+                ImmutableSet.of(123, 456, 789)
+        );
+
+        mService.agentDisconnected("com.android.foo");
+
+        verify(mOperationStorage).cancelOperation(eq(123), eq(true), any(IntConsumer.class));
+        verify(mOperationStorage).cancelOperation(eq(456), eq(true), any());
+        verify(mOperationStorage).cancelOperation(eq(789), eq(true), any());
+    }
+
+    @Test
+    public void testAgentDisconnected_unknownPackageName_cancelsNothing() throws Exception {
+        when(mOperationStorage.operationTokensForPackage(eq("com.android.foo"))).thenReturn(
+                ImmutableSet.of()
+        );
+
+        mService.agentDisconnected("com.android.foo");
+
+        verify(mOperationStorage, never())
+                .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class));
+    }
+
     private static PackageInfo getPackageInfo(String packageName) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -174,8 +207,9 @@
         boolean isEnabledStatePersisted = false;
         boolean shouldUseNewBackupEligibilityRules = false;
 
-        TestBackupService(Context context, PackageManager packageManager) {
-            super(context, packageManager);
+        TestBackupService(Context context, PackageManager packageManager,
+                LifecycleOperationStorage operationStorage) {
+            super(context, packageManager, operationStorage);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
index fa35e3f..3c79d8b 100644
--- a/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/internal/BackupHandlerTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.assertTrue;
 
 import android.os.HandlerThread;
@@ -28,6 +27,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.OperationStorage;
 import com.android.server.backup.UserBackupManagerService;
 
 import org.junit.After;
@@ -46,6 +46,7 @@
     private static final int MESSAGE_TIMEOUT_MINUTES = 1;
 
     @Mock private UserBackupManagerService mUserBackupManagerService;
+    @Mock private OperationStorage mOperationStorage;
     @Mock private BackupAgentTimeoutParameters mTimeoutParameters;
 
     private HandlerThread mHandlerThread;
@@ -114,7 +115,7 @@
         private final boolean mShouldStop;
 
         TestBackupHandler(boolean shouldStop) {
-            super(mUserBackupManagerService, mHandlerThread);
+            super(mUserBackupManagerService, mOperationStorage, mHandlerThread);
 
             mShouldStop = shouldStop;
         }
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
new file mode 100644
index 0000000..948accc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.backup.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.backup.BackupRestoreTask;
+import com.android.server.backup.OperationStorage.OpState;
+import com.android.server.backup.OperationStorage.OpType;
+
+import com.google.android.collect.Sets;
+
+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.util.Set;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LifecycleOperationStorageTest {
+    private static final int USER_ID = 0;
+    private static final int TOKEN_1 = 1;
+    private static final int TOKEN_2 = 2;
+    private static final int TOKEN_3 = 3;
+    private static final long RESULT = 123L;
+
+    private static final String PKG_FOO = "com.android.foo";
+    private static final String PKG_BAR = "com.android.bar";
+    private static final String PKG_BAZ = "com.android.baz";
+    private static final Set<String> MULTIPLE_PKG    = Sets.newHashSet(PKG_FOO);
+    private static final Set<String> MULTIPLE_PKGS_1 = Sets.newHashSet(PKG_FOO, PKG_BAR);
+    private static final Set<String> MULTIPLE_PKGS_2 = Sets.newHashSet(PKG_BAR, PKG_BAZ);
+
+    @Mock private BackupRestoreTask mCallback;
+    private LifecycleOperationStorage mOpStorage;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(/* testClass */ this);
+        mOpStorage = new LifecycleOperationStorage(USER_ID);
+    }
+
+    @After
+    public void tearDown() {}
+
+    @Test
+    public void testRegisterOperation_singleOperation() throws Exception {
+        mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+
+        Set<Integer> tokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(1);
+        assertThat(tokens).isEqualTo(only(TOKEN_1));
+    }
+
+    @Test
+    public void testRegisterOperation_multipleOperations() throws Exception {
+        mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+        mOpStorage.registerOperation(TOKEN_2, OpState.ACKNOWLEDGED, mCallback, OpType.BACKUP_WAIT);
+
+        Set<Integer> typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+        Set<Integer> statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+        Set<Integer> stateAcknowledgedTokens =
+                mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(2);
+        assertThat(typeWaitTokens).isEqualTo(Sets.newHashSet(TOKEN_1, TOKEN_2));
+        assertThat(statePendingTokens).isEqualTo(only(TOKEN_1));
+        assertThat(stateAcknowledgedTokens).isEqualTo(only(TOKEN_2));
+    }
+
+    @Test
+    public void testRegisterOperationForPackages_singlePackage() throws Exception {
+        mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING,
+                MULTIPLE_PKG, mCallback, OpType.BACKUP_WAIT);
+
+        Set<Integer> tokens = mOpStorage.operationTokensForPackage(PKG_FOO);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(1);
+        assertThat(tokens).isEqualTo(only(TOKEN_1));
+    }
+
+    @Test
+    public void testRegisterOperationForPackages_multiplePackage() throws Exception {
+        mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING,
+                MULTIPLE_PKGS_1, mCallback, OpType.BACKUP);
+        mOpStorage.registerOperationForPackages(TOKEN_2, OpState.PENDING,
+                MULTIPLE_PKGS_2, mCallback, OpType.BACKUP);
+
+        Set<Integer> tokensFoo = mOpStorage.operationTokensForPackage(PKG_FOO);
+        Set<Integer> tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR);
+        Set<Integer> tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(2);
+        assertThat(tokensFoo).isEqualTo(only(TOKEN_1));
+        assertThat(tokensBar).isEqualTo(Sets.newHashSet(TOKEN_1, TOKEN_2));
+        assertThat(tokensBaz).isEqualTo(only(TOKEN_2));
+    }
+
+    @Test
+    public void testRemoveOperation() throws Exception {
+        mOpStorage.registerOperation(TOKEN_2, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+
+        Set<Integer> typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+        Set<Integer> statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(1);
+        assertThat(typeWaitTokens).isEqualTo(only(TOKEN_2));
+        assertThat(statePendingTokens).isEqualTo(only(TOKEN_2));
+
+        mOpStorage.removeOperation(TOKEN_2);
+
+        typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+        statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(0);
+        assertThat(typeWaitTokens).isEmpty();
+        assertThat(statePendingTokens).isEmpty();
+    }
+
+    @Test
+    public void testRemoveOperation_removesPackageMappings() throws Exception {
+        mOpStorage.registerOperationForPackages(TOKEN_1, OpState.PENDING, MULTIPLE_PKGS_1,
+                mCallback, OpType.BACKUP);
+        mOpStorage.registerOperationForPackages(TOKEN_2, OpState.PENDING, MULTIPLE_PKGS_2,
+                mCallback, OpType.BACKUP);
+
+        mOpStorage.removeOperation(TOKEN_2);
+
+        Set<Integer> tokensFoo = mOpStorage.operationTokensForPackage(PKG_FOO);
+        Set<Integer> tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR);
+        Set<Integer> tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ);
+
+        assertThat(mOpStorage.numOperations()).isEqualTo(1);
+        assertThat(tokensFoo).isEqualTo(only(TOKEN_1));
+        assertThat(tokensBar).isEqualTo(only(TOKEN_1));
+        assertThat(tokensBaz).isEmpty();
+    }
+
+    @Test
+    public void testIsBackupOperationInProgress() throws Exception {
+        mOpStorage.registerOperation(TOKEN_1, OpState.ACKNOWLEDGED, mCallback, OpType.RESTORE_WAIT);
+        assertThat(mOpStorage.isBackupOperationInProgress()).isFalse();
+
+        mOpStorage.registerOperation(TOKEN_2, OpState.TIMEOUT, mCallback, OpType.BACKUP_WAIT);
+        assertThat(mOpStorage.isBackupOperationInProgress()).isFalse();
+
+        mOpStorage.registerOperation(TOKEN_3, OpState.PENDING, mCallback, OpType.BACKUP);
+        assertThat(mOpStorage.isBackupOperationInProgress()).isTrue();
+    }
+
+    @Test
+    public void testOnOperationComplete_pendingAdvancesState_invokesCallback() throws Exception {
+        mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
+
+        mOpStorage.onOperationComplete(TOKEN_1, RESULT, callback -> {
+            mCallback.operationComplete(RESULT);
+        });
+
+        assertThat(mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT))
+                .isEqualTo(only(TOKEN_1));
+        assertThat(mOpStorage.operationTokensForOpState(OpState.PENDING)).isEmpty();
+        assertThat(mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED)).isNotEmpty();
+        verify(mCallback).operationComplete(RESULT);
+    }
+
+    private Set<Integer> only(Integer val) {
+        return Sets.newHashSet(val);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index b3f7587..b255a35 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -302,6 +302,65 @@
         testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
     }
 
+    // TODO (b/208484275) : Enable these tests
+    // @Test
+    // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
+    //     SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+    //     when(manager
+    //             .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+    //             .thenReturn(false);
+    //     when(mContext.getSystemService(SensorPrivacyManager.class))
+    //             .thenReturn(manager);
+    //     setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+    //             mock(IBiometricAuthenticator.class));
+    //     final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+    //     final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+    //     assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+    //     for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+    //         assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+    //     }
+    // }
+
+    // @Test
+    // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception {
+    //     SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+    //     when(manager
+    //             .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+    //             .thenReturn(true);
+    //     when(mContext.getSystemService(SensorPrivacyManager.class))
+    //             .thenReturn(manager);
+    //     setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+    //             mock(IBiometricAuthenticator.class));
+    //     final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+    //     final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+    //     assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED,
+    //             preAuthInfo.getCanAuthenticateResult());
+    //     // Even though canAuth returns privacy enabled, we should still be able to authenticate.
+    //     for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+    //         assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+    //     }
+    // }
+
+    // @Test
+    // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception {
+    //     SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+    //     when(manager
+    //             .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+    //             .thenReturn(true);
+    //     when(mContext.getSystemService(SensorPrivacyManager.class))
+    //             .thenReturn(manager);
+    //     setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+    //             mock(IBiometricAuthenticator.class));
+    //     final PromptInfo promptInfo =
+    //             createPromptInfo(Authenticators.BIOMETRIC_STRONG
+    //             | Authenticators. DEVICE_CREDENTIAL);
+    //     final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+    //     assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+    //     for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+    //         assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+    //     }
+    // }
+
     private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException {
         final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
 
@@ -331,7 +390,8 @@
                 userId,
                 promptInfo,
                 TEST_PACKAGE,
-                checkDevicePolicyManager);
+                checkDevicePolicyManager,
+                mContext);
     }
 
     private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
index 8765c9a..6a2192a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/biometrics/OWNERS
@@ -1,7 +1 @@
-set noparent
-
-kchyn@google.com
-jaggies@google.com
-curtislb@google.com
-ilyamaty@google.com
-joshmccloskey@google.com
+include /services/core/java/com/android/server/biometrics/OWNERS
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
new file mode 100644
index 0000000..c7c0756
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.graphics.Point;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.KeyEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class VirtualDeviceManagerServiceTest {
+
+    private static final String DEVICE_NAME = "device name";
+    private static final int DISPLAY_ID = 2;
+    private static final int PRODUCT_ID = 10;
+    private static final int VENDOR_ID = 5;
+    private static final int HEIGHT = 1800;
+    private static final int WIDTH = 900;
+    private static final Binder BINDER = new Binder("binder");
+
+    private Context mContext;
+    private VirtualDeviceImpl mDeviceImpl;
+    private InputController mInputController;
+    @Mock
+    private InputController.NativeWrapper mNativeWrapperMock;
+    @Mock
+    private DisplayManagerInternal mDisplayManagerInternalMock;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
+
+        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+        mInputController = new InputController(new Object(), mNativeWrapperMock);
+        mDeviceImpl = new VirtualDeviceImpl(mContext,
+                /* association info */ null, new Binder(), /* uid */ 0, mInputController,
+                (int associationId) -> {});
+    }
+
+    @Test
+    public void createVirtualKeyboard_noDisplay_failsSecurityException() {
+        assertThrows(
+                SecurityException.class,
+                () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+                        PRODUCT_ID, BINDER));
+    }
+
+    @Test
+    public void createVirtualMouse_noDisplay_failsSecurityException() {
+        assertThrows(
+                SecurityException.class,
+                () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
+                        PRODUCT_ID, 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)));
+    }
+
+    @Test
+    public void createVirtualKeyboard_noPermission_failsSecurityException() {
+        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));
+    }
+
+    @Test
+    public void createVirtualMouse_noPermission_failsSecurityException() {
+        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));
+    }
+
+    @Test
+    public void createVirtualTouchscreen_noPermission_failsSecurityException() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        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)));
+    }
+
+    @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.mInputDeviceFds).isNotEmpty();
+        verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+    }
+
+    @Test
+    public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
+                BINDER);
+        assertWithMessage("Virtual keyboard should register fd when the display matches")
+                .that(mInputController.mInputDeviceFds).isNotEmpty();
+        verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID);
+    }
+
+    @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 keyboard should register fd when the display matches")
+                .that(mInputController.mInputDeviceFds).isNotEmpty();
+        verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT,
+                WIDTH);
+    }
+
+    @Test
+    public void sendKeyEvent_noFd() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
+                                .setKeyCode(KeyEvent.KEYCODE_A)
+                                .setAction(VirtualKeyEvent.ACTION_DOWN).build()));
+    }
+
+    @Test
+    public void sendKeyEvent_hasFd_writesEvent() {
+        final int fd = 1;
+        final int keyCode = KeyEvent.KEYCODE_A;
+        final int action = VirtualKeyEvent.ACTION_UP;
+        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
+                .setAction(action).build());
+        verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
+    }
+
+    @Test
+    public void sendButtonEvent_noFd() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mDeviceImpl.sendButtonEvent(BINDER,
+                                new VirtualMouseButtonEvent.Builder()
+                                        .setButtonCode(VirtualMouseButtonEvent.BUTTON_BACK)
+                                        .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
+                                        .build()));
+    }
+
+    @Test
+    public void sendButtonEvent_hasFd_writesEvent() {
+        final int fd = 1;
+        final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
+        final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
+        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
+                .setButtonCode(buttonCode)
+                .setAction(action).build());
+        verify(mNativeWrapperMock).writeButtonEvent(fd, buttonCode, action);
+    }
+
+    @Test
+    public void sendRelativeEvent_noFd() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mDeviceImpl.sendRelativeEvent(BINDER,
+                                new VirtualMouseRelativeEvent.Builder().setRelativeX(
+                                        0.0f).setRelativeY(0.0f).build()));
+    }
+
+    @Test
+    public void sendRelativeEvent_hasFd_writesEvent() {
+        final int fd = 1;
+        final float x = -0.2f;
+        final float y = 0.7f;
+        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
+                .setRelativeX(x).setRelativeY(y).build());
+        verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y);
+    }
+
+    @Test
+    public void sendScrollEvent_noFd() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mDeviceImpl.sendScrollEvent(BINDER,
+                                new VirtualMouseScrollEvent.Builder()
+                                        .setXAxisMovement(-1f)
+                                        .setYAxisMovement(1f).build()));
+    }
+
+    @Test
+    public void sendScrollEvent_hasFd_writesEvent() {
+        final int fd = 1;
+        final float x = 0.5f;
+        final float y = 1f;
+        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
+                .setXAxisMovement(x)
+                .setYAxisMovement(y).build());
+        verify(mNativeWrapperMock).writeScrollEvent(fd, x, y);
+    }
+
+    @Test
+    public void sendTouchEvent_noFd() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
+                                .setX(0.0f)
+                                .setY(0.0f)
+                                .setAction(VirtualTouchEvent.ACTION_UP)
+                                .setPointerId(1)
+                                .setToolType(VirtualTouchEvent.TOOL_TYPE_FINGER)
+                                .build()));
+    }
+
+    @Test
+    public void sendTouchEvent_hasFd_writesEvent_withoutPressureOrMajorAxisSize() {
+        final int fd = 1;
+        final int pointerId = 5;
+        final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER;
+        final float x = 100.5f;
+        final float y = 200.5f;
+        final int action = VirtualTouchEvent.ACTION_UP;
+        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
+                .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
+        verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
+                Float.NaN);
+    }
+
+    @Test
+    public void sendTouchEvent_hasFd_writesEvent() {
+        final int fd = 1;
+        final int pointerId = 5;
+        final int toolType = VirtualTouchEvent.TOOL_TYPE_FINGER;
+        final float x = 100.5f;
+        final float y = 200.5f;
+        final int action = VirtualTouchEvent.ACTION_UP;
+        final float pressure = 1.0f;
+        final float majorAxisSize = 10.0f;
+        mInputController.mInputDeviceFds.put(BINDER, fd);
+        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
+                .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
+                .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
+        verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,
+                majorAxisSize);
+    }
+}
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 f664517..abe7d89 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -21,7 +21,9 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.anyInt;
+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.content.Context;
@@ -63,6 +65,7 @@
 
     @Mock SensorManager mSensorManager;
     @Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
+    @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
     @Mock HysteresisLevels mAmbientBrightnessThresholds;
     @Mock HysteresisLevels mScreenBrightnessThresholds;
     @Mock Handler mNoOpHandler;
@@ -100,7 +103,7 @@
                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
                 mAmbientBrightnessThresholds, mScreenBrightnessThresholds,
-                mContext, mHbmController
+                mContext, mHbmController, mIdleBrightnessMappingStrategy
         );
 
         when(mHbmController.getCurrentBrightnessMax()).thenReturn(BRIGHTNESS_MAX_FLOAT);
@@ -231,4 +234,44 @@
         // There should be a user data point added to the mapper.
         verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
     }
+
+    @Test
+    public void testSwitchToIdleMappingStrategy() throws Exception {
+        Sensor lightSensor = TestUtils.createSensor(Sensor.TYPE_LIGHT, "Light Sensor");
+        mController = setupController(lightSensor);
+
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(lightSensor),
+                eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+        SensorEventListener listener = listenerCaptor.getValue();
+
+        // Sensor reads 1000 lux,
+        listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000));
+
+        // User sets brightness to 100
+        mController.configure(true /* enable */, null /* configuration */,
+                0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
+                false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+
+        // There should be a user data point added to the mapper.
+        verify(mBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
+        verify(mBrightnessMappingStrategy, times(2)).setBrightnessConfiguration(any());
+        verify(mBrightnessMappingStrategy, times(3)).getBrightness(anyFloat(), any(), anyInt());
+
+        // Now let's do the same for idle mode
+        mController.switchToIdleMode();
+        // Called once for init, and once when switching
+        verify(mBrightnessMappingStrategy, times(2)).isForIdleMode();
+        // Ensure, after switching, original BMS is not used anymore
+        verifyNoMoreInteractions(mBrightnessMappingStrategy);
+
+        // User sets idle brightness to 0.5
+        mController.configure(true /* enable */, null /* configuration */,
+                0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
+                false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
+
+        // Ensure we use the correct mapping strategy
+        verify(mIdleBrightnessMappingStrategy, times(1)).addUserDataPoint(1000f, 0.5f);
+    }
 }
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 68e90fb..eaa271a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -219,7 +219,7 @@
         builder.setUniqueId(uniqueId);
         builder.setFlags(flags);
         int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -341,7 +341,7 @@
         builder.setFlags(flags);
         builder.setUniqueId(uniqueId);
         int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -580,7 +580,8 @@
                 VIRTUAL_DISPLAY_NAME, width, height, dpi);
         builder.setUniqueId(uniqueId);
         final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken /* callback */, null /* projection */, null /* virtualDeviceToken */,
+                PACKAGE_NAME);
 
         // The second virtual display requests to mirror the first virtual display.
         final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -590,7 +591,8 @@
         builder2.setUniqueId(uniqueId2);
         builder2.setDisplayIdToMirror(firstDisplayId);
         final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
-                mMockAppToken2 /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken2 /* callback */, null /* projection */,
+                null /* virtualDeviceToken */, PACKAGE_NAME);
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
         // flush the handler
@@ -628,7 +630,8 @@
         builder.setSurface(surface);
         builder.setUniqueId(uniqueId);
         final int displayId = binderService.createVirtualDisplay(builder.build(),
-                mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+                mMockAppToken /* callback */, null /* projection */, null /* virtualDeviceToken */,
+                PACKAGE_NAME);
 
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
 
@@ -1108,7 +1111,7 @@
         builder.setUniqueId(uniqueId);
 
         int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
-                null /* projection */, PACKAGE_NAME);
+                null /* projection */, null /* virtualDeviceToken */, PACKAGE_NAME);
         displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
         displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
         DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
new file mode 100644
index 0000000..70e78eb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -0,0 +1,740 @@
+/*
+ * 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.locales;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+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.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.SimpleClock;
+import android.util.AtomicFile;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.XmlUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for the {@link LocaleManagerInternal}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleManagerBackupRestoreTest {
+    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
+    private static final String TEST_LOCALES_XML_TAG = "locales";
+    private static final int DEFAULT_USER_ID = 0;
+    private static final int WORK_PROFILE_USER_ID = 10;
+    private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+    private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
+    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 File STAGED_LOCALES_DIR = new File(
+            Environment.getExternalStorageDirectory(), "lmsUnitTests");
+
+
+    private LocaleManagerBackupHelper mBackupHelper;
+    private long mCurrentTimeMillis;
+
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+    @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
+    private LocaleManagerService mMockLocaleManagerService;
+    BroadcastReceiver mUserMonitor;
+    PackageMonitor mPackageMonitor;
+
+    private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
+        @Override
+        public long millis() {
+            return currentTimeMillis();
+        }
+    };
+
+    private long currentTimeMillis() {
+        return mCurrentTimeMillis;
+    }
+
+    private void setCurrentTimeMillis(long currentTimeMillis) {
+        mCurrentTimeMillis = currentTimeMillis;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mMockContext = mock(Context.class);
+        mMockPackageManagerInternal = mock(PackageManagerInternal.class);
+        mMockPackageManager = mock(PackageManager.class);
+        mMockLocaleManagerService = mock(LocaleManagerService.class);
+
+        doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+
+        mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
+                mMockLocaleManagerService, mMockPackageManagerInternal,
+                new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"), mClock));
+        doNothing().when(mBackupHelper).notifyBackupManager();
+
+        mUserMonitor = mBackupHelper.getUserMonitor();
+        mPackageMonitor = mBackupHelper.getPackageMonitor();
+        setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
+        cleanStagedFiles();
+    }
+
+    @Test
+    public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+    }
+
+    @Test
+    public void testBackupPayload_noAppLocalesSet_returnsNull() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+    }
+
+    @Test
+    public void testBackupPayload_appLocalesSet_returnsNonNullBlob() throws Exception {
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+
+        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
+        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
+    }
+
+    @Test
+    public void testBackupPayload_exceptionInGetLocalesAllPackages_returnsNull() throws Exception {
+        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
+        doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
+                anyString(), anyInt());
+
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+    }
+
+    @Test
+    public void testBackupPayload_exceptionInGetLocalesSomePackages_appsWithExceptionNotBackedUp()
+            throws Exception {
+        // Set up two apps.
+        ApplicationInfo defaultAppInfo = new ApplicationInfo();
+        ApplicationInfo anotherAppInfo = new ApplicationInfo();
+        defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
+        anotherAppInfo.packageName = "com.android.anotherapp";
+        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
+        // 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);
+    }
+
+    @Test
+    public void testRestore_nullPayload_nothingRestoredAndNoStageFile() throws Exception {
+        mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ null, DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_zeroLengthPayload_nothingRestoredAndNoStageFile() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        mBackupHelper.stageAndApplyRestoredPayload(/* payload= */ out.toByteArray(),
+                DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_allAppsInstalled_noStageFileCreated() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Locales were restored
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES);
+
+        // Stage file wasn't created.
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_noAppsInstalled_everythingStaged() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                getStageFileIfExists(DEFAULT_USER_ID), DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testRestore_someAppsInstalled_partiallyStaged() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru", langTagsB = "hi,fr";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        writeTestPayload(out, pkgLocalesMap);
+
+        setUpPackageInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA));
+
+        pkgLocalesMap.remove(pkgNameA);
+        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testRestore_appLocalesAlreadySet_nothingRestoredAndNoStageFile() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Since locales are already set, we should not restore anything for it.
+        verifyNothingRestored();
+        // Stage file wasn't created
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_appLocalesSetForSomeApps_restoresOnlyForAppsHavingNoLocalesSet()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB", pkgNameC =
+                "com.android.myAppC";
+        String langTagsA = "ru", langTagsB = "hi,fr", langTagsC = "zh,es";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        pkgLocalesMap.put(pkgNameC, langTagsC);
+        writeTestPayload(out, pkgLocalesMap);
+
+        // Both app A & B are installed on the device but A has locales already set.
+        setUpPackageInstalled(pkgNameA);
+        setUpPackageInstalled(pkgNameB);
+        setUpPackageNotInstalled(pkgNameC);
+        setUpLocalesForPackage(pkgNameA, LocaleList.forLanguageTags("mr,fr"));
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameC, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        // Restore locales only for myAppB.
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
+                any());
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsB));
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
+                any());
+
+        // App C is staged.
+        pkgLocalesMap.remove(pkgNameA);
+        pkgLocalesMap.remove(pkgNameB);
+        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testRestore_restoreInvokedAgain_creationTimeChanged() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        final long newCreationTime = DEFAULT_CREATION_TIME_MILLIS + 100;
+        setCurrentTimeMillis(newCreationTime);
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                newCreationTime);
+    }
+
+    @Test
+    public void testRestore_appInstalledAfterSUW_restoresFromStage() throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        HashMap<String, String> pkgLocalesMap = new HashMap<>();
+
+        String pkgNameA = "com.android.myAppA", pkgNameB = "com.android.myAppB";
+        String langTagsA = "ru", langTagsB = "hi,fr";
+        pkgLocalesMap.put(pkgNameA, langTagsA);
+        pkgLocalesMap.put(pkgNameB, langTagsB);
+        writeTestPayload(out, pkgLocalesMap);
+
+        setUpPackageNotInstalled(pkgNameA);
+        setUpPackageNotInstalled(pkgNameB);
+        setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList());
+        setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList());
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+
+        setUpPackageInstalled(pkgNameA);
+
+        mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsA));
+
+        pkgLocalesMap.remove(pkgNameA);
+        verifyStageFileContent(pkgLocalesMap, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        setUpPackageInstalled(pkgNameB);
+
+        mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
+                LocaleList.forLanguageTags(langTagsB));
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_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();
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        // App is installed later (post SUW).
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr"));
+
+        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        // Since locales are already set, we should not restore anything for it.
+        verifyNothingRestored();
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testStageFileDeletion_backupPassRunAfterRetentionPeriod_stageFileDeleted()
+            throws Exception {
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP);
+
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+
+        mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
+
+        verifyNothingRestored();
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, getStageFileIfExists(DEFAULT_USER_ID),
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        // Retention period has not elapsed.
+        setCurrentTimeMillis(
+                DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis());
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+
+        // Stage file should NOT be deleted.
+        checkStageFileExists(DEFAULT_USER_ID);
+
+        // Exactly RETENTION_PERIOD amount of time has passed so stage file should still not be
+        // removed.
+        setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.toMillis());
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+
+        // Stage file should NOT be deleted.
+        checkStageFileExists(DEFAULT_USER_ID);
+
+        // Retention period has now expired, stage file should be deleted.
+        setCurrentTimeMillis(
+                DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis());
+        doReturn(List.of()).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
+
+        // Stage file should be deleted.
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+    }
+
+    @Test
+    public void testUserRemoval_userRemoved_stageFileDeleted() throws Exception {
+        final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
+        writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_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);
+        writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
+
+        // DEFAULT_PACKAGE_NAME is NOT installed on the device.
+        setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+        setUpPackageNotInstalled(anotherPackage);
+
+        mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID);
+        mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(),
+                WORK_PROFILE_USER_ID);
+
+        verifyNothingRestored();
+
+        // Verify stage file contents.
+        AtomicFile stageFileDefaultUser = getStageFileIfExists(DEFAULT_USER_ID);
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP, stageFileDefaultUser,
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        AtomicFile stageFileWorkProfile = getStageFileIfExists(WORK_PROFILE_USER_ID);
+        verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS);
+
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_USER_REMOVED);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID);
+        mUserMonitor.onReceive(mMockContext, intent);
+
+        // Stage file should be removed only for DEFAULT_USER_ID.
+        checkStageFileDoesNotExist(DEFAULT_USER_ID);
+        verifyStageFileContent(pkgLocalesMapWorkProfile, stageFileWorkProfile,
+                DEFAULT_CREATION_TIME_MILLIS);
+    }
+
+    @Test
+    public void testLoadStageFiles_invalidNameFormat_stageFileDeleted() throws Exception {
+        // Stage file name should be : staged_locales_<user_id_int>.xml
+        File stageFile = new File(STAGED_LOCALES_DIR, "xyz.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        // Putting valid xml data in file.
+        FileOutputStream out = new FileOutputStream(stageFile);
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
+                true, /* creationTimeMillis= */ 0);
+        out.flush();
+        out.close();
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        assertFalse(stageFile.isFile());
+    }
+
+    @Test
+    public void testLoadStageFiles_userIdNotParseable_stageFileDeleted() throws Exception {
+        // Stage file name should be : staged_locales_<user_id_int>.xml
+        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_abc.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        // Putting valid xml data in file.
+        FileOutputStream out = new FileOutputStream(stageFile);
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
+                true, /* creationTimeMillis= */ 0);
+        out.flush();
+        out.close();
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                new AtomicFile(stageFile), /* creationTimeMillis= */ 0);
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        assertFalse(stageFile.isFile());
+    }
+
+    @Test
+    public void testLoadStageFiles_invalidContent_stageFileDeleted() throws Exception {
+        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        FileOutputStream out = new FileOutputStream(stageFile);
+        out.write("some_non_xml_string".getBytes());
+        out.close();
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        assertFalse(stageFile.isFile());
+    }
+
+    @Test
+    public void testLoadStageFiles_validContent_doesLazyRestore() throws Exception {
+        File stageFile = new File(STAGED_LOCALES_DIR, "staged_locales_0.xml");
+        assertTrue(stageFile.createNewFile());
+        assertTrue(stageFile.isFile());
+
+        // Putting valid xml data in file.
+        FileOutputStream out = new FileOutputStream(stageFile);
+        writeTestPayload(out, DEFAULT_PACKAGE_LOCALES_MAP, /* forStage= */
+                true, DEFAULT_CREATION_TIME_MILLIS);
+        out.flush();
+        out.close();
+
+        verifyStageFileContent(DEFAULT_PACKAGE_LOCALES_MAP,
+                new AtomicFile(stageFile), DEFAULT_CREATION_TIME_MILLIS);
+
+        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
+                mMockPackageManagerInternal, STAGED_LOCALES_DIR, mClock);
+        mPackageMonitor = mBackupHelper.getPackageMonitor();
+
+        // Stage file still exists.
+        assertTrue(stageFile.isFile());
+
+        // App is installed later.
+        setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+
+        mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+        verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+                DEFAULT_USER_ID, DEFAULT_LOCALES);
+
+        // Stage file gets deleted here because all staged locales have been applied.
+        assertFalse(stageFile.isFile());
+    }
+
+    private void setUpPackageInstalled(String packageName) throws Exception {
+        doReturn(new PackageInfo()).when(mMockPackageManager).getPackageInfoAsUser(
+                eq(packageName), anyInt(), anyInt());
+    }
+
+    private void setUpPackageNotInstalled(String packageName) throws Exception {
+        doReturn(null).when(mMockPackageManager).getPackageInfoAsUser(eq(packageName),
+                anyInt(), anyInt());
+    }
+
+    private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
+        doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(
+                eq(packageName), anyInt());
+    }
+
+    private void setUpDummyAppForPackageManager(String packageName) {
+        ApplicationInfo dummyApp = new ApplicationInfo();
+        dummyApp.packageName = packageName;
+        doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
+                .getInstalledApplications(anyLong(), anyInt(), anyInt());
+    }
+
+    /**
+     * Verifies that nothing was restored for any package.
+     *
+     * <p>If {@link LocaleManagerService#setApplicationLocales} is not invoked, we can conclude
+     * that nothing was restored.
+     */
+    private void verifyNothingRestored() throws Exception {
+        verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
+                any());
+    }
+
+
+    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
+            byte[] payload)
+            throws IOException, XmlPullParserException {
+        verifyPayloadForAppLocales(expectedPkgLocalesMap, payload, /* forStage= */ false, -1);
+    }
+
+    private static void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
+            byte[] payload, boolean forStage, long expectedCreationTime)
+            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<>();
+        XmlUtils.beginDocument(parser, TEST_LOCALES_XML_TAG);
+        if (forStage) {
+            long actualCreationTime = parser.getAttributeLong(/* namespace= */ null,
+                    "creationTimeMillis");
+            assertEquals(expectedCreationTime, actualCreationTime);
+        }
+        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);
+            }
+        }
+
+        assertEquals(expectedPkgLocalesMap, backupDataMap);
+    }
+
+    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap)
+            throws IOException {
+        writeTestPayload(stream, pkgLocalesMap, /* forStage= */ false, /* creationTimeMillis= */
+                -1);
+    }
+
+    private static void writeTestPayload(OutputStream stream, Map<String, String> pkgLocalesMap,
+            boolean forStage, long creationTimeMillis)
+            throws IOException {
+        if (pkgLocalesMap.isEmpty()) {
+            return;
+        }
+
+        TypedXmlSerializer out = Xml.newFastSerializer();
+        out.setOutput(stream, StandardCharsets.UTF_8.name());
+        out.startDocument(/* encoding= */ null, /* standalone= */ true);
+        out.startTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
+
+        if (forStage) {
+            out.attribute(/* namespace= */ null, "creationTimeMillis",
+                    Long.toString(creationTimeMillis));
+        }
+
+        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.endTag(/*namespace= */ null, "package");
+        }
+
+        out.endTag(/* namespace= */ null, TEST_LOCALES_XML_TAG);
+        out.endDocument();
+    }
+
+    private static void verifyStageFileContent(Map<String, String> expectedPkgLocalesMap,
+            AtomicFile stageFile,
+            long creationTimeMillis)
+            throws Exception {
+        assertNotNull(stageFile);
+        try (InputStream stagedDataInputStream = stageFile.openRead()) {
+            verifyPayloadForAppLocales(expectedPkgLocalesMap, stagedDataInputStream.readAllBytes(),
+                    /* forStage= */ true, creationTimeMillis);
+        } catch (IOException | XmlPullParserException e) {
+            throw e;
+        }
+    }
+
+    private static void checkStageFileDoesNotExist(int userId) {
+        assertNull(getStageFileIfExists(userId));
+    }
+
+    private static void checkStageFileExists(int userId) {
+        assertNotNull(getStageFileIfExists(userId));
+    }
+
+    private static AtomicFile getStageFileIfExists(int userId) {
+        File file = new File(STAGED_LOCALES_DIR, String.format("staged_locales_%d.xml", userId));
+        if (file.isFile()) {
+            return new AtomicFile(file);
+        }
+        return null;
+    }
+
+    private static void cleanStagedFiles() {
+        File[] files = STAGED_LOCALES_DIR.listFiles();
+        if (files != null) {
+            for (File f : files) {
+                f.delete();
+            }
+        }
+    }
+}
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 ddc58b2..658f8d5 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.Manifest;
@@ -60,7 +61,7 @@
     private static final int DEFAULT_USER_ID = 0;
     private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
     private static final int INVALID_UID = -1;
-    private static final String DEFAULT_LOCALE_TAGS = "en-XC, ar-XB";
+    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
     private static final LocaleList DEFAULT_LOCALES =
             LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
     private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo(
@@ -68,6 +69,7 @@
             /* originatingPackageName = */ null, /* installingPackageName = */ null);
 
     private LocaleManagerService mLocaleManagerService;
+    private LocaleManagerBackupHelper mMockBackupHelper;
 
     @Mock
     private Context mMockContext;
@@ -104,8 +106,9 @@
                 .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
                         anyString(), anyString());
 
+        mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
         mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
-                mMockActivityManager, mMockPackageManagerInternal);
+                mMockActivityManager, mMockPackageManagerInternal, mMockBackupHelper);
     }
 
     @Test(expected = SecurityException.class)
@@ -122,6 +125,7 @@
             verify(mMockContext).enforceCallingOrSelfPermission(
                     eq(android.Manifest.permission.CHANGE_CONFIGURATION),
                     anyString());
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
         }
     }
@@ -133,6 +137,7 @@
                     DEFAULT_USER_ID, LocaleList.getEmptyLocaleList());
             fail("Expected NullPointerException");
         } finally {
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
         }
     }
@@ -146,6 +151,7 @@
                     /* locales = */ null);
             fail("Expected NullPointerException");
         } finally {
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
         }
     }
@@ -163,6 +169,7 @@
                 DEFAULT_LOCALES);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
+        verify(mMockBackupHelper, times(1)).notifyBackupManager();
 
     }
 
@@ -175,6 +182,7 @@
                 DEFAULT_LOCALES);
 
         assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
+        verify(mMockBackupHelper, times(1)).notifyBackupManager();
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -187,6 +195,7 @@
             fail("Expected IllegalArgumentException");
         } finally {
             assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
+            verify(mMockBackupHelper, times(0)).notifyBackupManager();
         }
     }
 
@@ -217,7 +226,7 @@
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales = mLocaleManagerService.getApplicationLocales(
-                    DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+                DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
 
         assertEquals(LocaleList.getEmptyLocaleList(), locales);
     }
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
new file mode 100644
index 0000000..93972c3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locales;
+
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+
+import java.io.File;
+import java.time.Clock;
+
+/**
+ * Shadow for {@link LocaleManagerBackupHelper} to enable mocking it for tests.
+ *
+ * <p>{@link LocaleManagerBackupHelper} is a package private class and hence not mockable directly.
+ */
+public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
+    ShadowLocaleManagerBackupHelper(Context context,
+            LocaleManagerService localeManagerService,
+            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
+        super(context, localeManagerService, pmInternal, stagedLocalesDir, clock);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
index 847fe2e..7f7c716 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -61,6 +61,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Map;
 
 @SmallTest
 @Presubmit
@@ -130,6 +131,17 @@
     }
 
     @Test
+    public void testGetApexSystemServices() throws RemoteException {
+        when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, false));
+        mApexManager.scanApexPackagesTraced(mPackageParser2,
+                ParallelPackageParser.makeExecutorService());
+
+        Map<String, String> services = mApexManager.getApexSystemServices();
+        assertThat(services).hasSize(1);
+        assertThat(services).containsKey("com.android.apex.test.ApexSystemService");
+    }
+
+    @Test
     public void testGetActivePackages() throws RemoteException {
         when(mApexService.getAllPackages()).thenReturn(createApexInfoForTestPkg(true, true));
         mApexManager.scanApexPackagesTraced(mPackageParser2,
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 3722ba4..ea7804d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -579,7 +579,7 @@
         }
 
         @Override
-        public void verifyCallingPackage(String callingPackage) {
+        public void verifyCallingPackage(String callingPackage, int callerUid) {
             // SKIP
         }
 
@@ -1500,6 +1500,20 @@
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
     }
 
+    /**
+     * Make a hidden shortcut with an ID.
+     */
+    protected ShortcutInfo makeShortcutExcludedFromLauncher(String id) {
+        final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext, id)
+                .setActivity(new ComponentName(mClientContext.getPackageName(), "main"))
+                .setShortLabel("Title-" + id)
+                .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class))
+                .setExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER);
+        final ShortcutInfo s = b.build();
+        s.setTimestamp(mInjectedCurrentTimeMillis);
+        return s;
+    }
+
     @Deprecated // Title was renamed to short label.
     protected ShortcutInfo makeShortcutWithTitle(String id, String title) {
         return makeShortcut(
@@ -1889,6 +1903,18 @@
         assertEquals("Exception type different", expectedException, thrown.getClass());
     }
 
+    protected void assertThrown(@NonNull final Class<?> expectedException,
+            @NonNull final Runnable fn) {
+        Exception thrown = null;
+        try {
+            fn.run();
+        } catch (Exception e) {
+            thrown = e;
+        }
+        assertNotNull("Exception was not thrown", thrown);
+        assertEquals("Exception type different", expectedException, thrown.getClass());
+    }
+
     protected void assertBitmapDirectories(int userId, String... expectedDirectories) {
         final Set<String> expected = hashSet(set(expectedDirectories));
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index b81a4ef..050b224 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -629,17 +629,19 @@
     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_POLICY
-            runShellCommand("pm install --install-reason 1 " + testApk);
+            // 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_POLICY);
+                            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_POLICY);
+                            UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
         } finally {
             runShellCommand("pm uninstall " + TEST_PKG_NAME);
         }
@@ -653,11 +655,12 @@
         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_POLICY
-            runShellCommand("pm install --install-reason 1 " + testApk);
+            // 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_POLICY);
+                            UserHandle.myUserId())).isEqualTo(
+                    PackageManager.INSTALL_REASON_DEVICE_SETUP);
 
             // Create and start the 2nd user.
             userId = um.createUser("Test User", 0 /* flags */).getUserHandle().getIdentifier();
@@ -692,8 +695,8 @@
             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_POLICY
-            runShellCommand("pm install --install-reason 1 " + testApk);
+            // 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(
@@ -718,11 +721,12 @@
             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_POLICY
-            runShellCommand("pm install --user cur --install-reason 1 " + testApk);
+            // 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_POLICY);
+                            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);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 11bac45..c888524 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -46,6 +46,7 @@
 import android.content.pm.parsing.ParsingPackage;
 import android.content.pm.parsing.component.ParsedActivity;
 import android.content.pm.parsing.component.ParsedActivityImpl;
+import android.content.pm.parsing.component.ParsedApexSystemService;
 import android.content.pm.parsing.component.ParsedComponent;
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedInstrumentationImpl;
@@ -526,6 +527,23 @@
     }
 
     @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 {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 71d5b77..28f24f2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -470,9 +470,7 @@
                 .addUsesPermission(
                         new ParsedUsesPermissionImpl(Manifest.permission.FACTORY_TEST, 0));
 
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
-                mMockPackageManager, mMockInjector);
-        final ScanResult scanResult = scanPackageHelper.scanPackageOnlyLI(
+        final ScanResult scanResult = ScanPackageUtils.scanPackageOnlyLI(
                 createBasicScanRequestBuilder(basicPackage).build(),
                 mMockInjector,
                 true /*isUnderFactoryTest*/,
@@ -520,9 +518,7 @@
 
     private ScanResult executeScan(
             ScanRequest scanRequest) throws PackageManagerException {
-        final ScanPackageHelper scanPackageHelper = new ScanPackageHelper(
-                mMockPackageManager, mMockInjector);
-        ScanResult result = scanPackageHelper.scanPackageOnlyLI(
+        ScanResult result = ScanPackageUtils.scanPackageOnlyLI(
                 scanRequest,
                 mMockInjector,
                 false /*isUnderFactoryTest*/,
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 32a88bd..a350dfb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -8900,6 +8900,48 @@
                 filter_any));
     }
 
+    public void testAddingShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+        final ShortcutInfo s1 = makeShortcutExcludedFromLauncher("s1");
+        final ShortcutInfo s2 = makeShortcutExcludedFromLauncher("s2");
+        final ShortcutInfo s3 = makeShortcutExcludedFromLauncher("s3");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(s1, s2, s3)));
+            assertEmpty(mManager.getDynamicShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.addDynamicShortcuts(list(s1, s2, s3)));
+            assertEmpty(mManager.getDynamicShortcuts());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            mManager.pushDynamicShortcut(s1);
+            assertEmpty(mManager.getDynamicShortcuts());
+        });
+    }
+
+    public void testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+        final ShortcutInfo s1 = makeShortcut("s1");
+        final ShortcutInfo s2 = makeShortcut("s2");
+        final ShortcutInfo s3 = makeShortcut("s3");
+
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(s1, s2, s3)));
+            assertThrown(IllegalArgumentException.class, () -> {
+                mManager.updateShortcuts(list(makeShortcutExcludedFromLauncher("s1")));
+            });
+        });
+    }
+
+    public void testPinHiddenShortcuts_ThrowsException() {
+        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+            assertThrown(IllegalArgumentException.class, () -> {
+                mManager.requestPinShortcut(makeShortcutExcludedFromLauncher("s1"), null);
+            });
+        });
+    }
+
     private Uri getFileUriFromResource(String fileName, int resId) throws IOException {
         File file = new File(getTestContext().getFilesDir(), fileName);
         // Make sure we are not leaving phantom files behind.
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
index bcd216d..0708be2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest12.java
@@ -257,6 +257,28 @@
         assertEquals("custom", map.get("s3").getShortLabel());
     }
 
+    public void testShortcutsExcludedFromLauncher_PersistedToDisk() {
+        if (!mService.isAppSearchEnabled()) {
+            return;
+        }
+        setCaller(CALLING_PACKAGE_1, USER_0);
+        mManager.setDynamicShortcuts(list(
+                makeShortcutExcludedFromLauncher("s1"),
+                makeShortcutExcludedFromLauncher("s2"),
+                makeShortcutExcludedFromLauncher("s3"),
+                makeShortcutExcludedFromLauncher("s4"),
+                makeShortcutExcludedFromLauncher("s5")
+        ));
+        final List<ShortcutInfo> shortcuts = getAllPersistedShortcuts();
+        assertNotNull(shortcuts);
+        assertEquals(5, shortcuts.size());
+        final Map<String, ShortcutInfo> map = shortcuts.stream()
+                .collect(Collectors.toMap(ShortcutInfo::getId, Function.identity()));
+        assertTrue(map.containsKey("s3"));
+        assertEquals("Title-s3", map.get("s3").getShortLabel());
+    }
+
+
     private List<ShortcutInfo> getAllPersistedShortcuts() {
         try {
             SystemClock.sleep(500);
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java b/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java
new file mode 100644
index 0000000..f172279
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java
@@ -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.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * {@link ContextWrapper} that doesn't register {@link BroadcastReceiver}.
+ *
+ * Instead, it keeps a list of the registrations for querying.
+ */
+class NoBroadcastContextWrapper extends TestableContext {
+
+    ArrayList<BroadcastReceiverRegistration> mRegistrationList =
+            new ArrayList<>();
+
+    NoBroadcastContextWrapper(Context context) {
+        super(context);
+    }
+
+    @Override
+    public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+        return registerReceiver(receiver, filter, 0);
+    }
+
+    @Override
+    public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+            int flags) {
+        return registerReceiver(receiver, filter, null, null, flags);
+    }
+
+    @Override
+    public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+            @Nullable String broadcastPermission, @Nullable Handler scheduler) {
+        return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0);
+    }
+
+    @Override
+    public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+            @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags) {
+        return registerReceiverAsUser(receiver, getUser(), filter, broadcastPermission, scheduler,
+                flags);
+    }
+
+    @Nullable
+    @Override
+    public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+            @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+            @Nullable Handler scheduler) {
+        return registerReceiverForAllUsers(receiver, filter, broadcastPermission, scheduler, 0);
+    }
+
+    @Nullable
+    @Override
+    public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+            @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+            @Nullable Handler scheduler, int flags) {
+        return registerReceiverAsUser(receiver, UserHandle.ALL, filter, broadcastPermission,
+                scheduler, flags);
+    }
+
+    @Override
+    public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+            IntentFilter filter, @Nullable String broadcastPermission,
+            @Nullable Handler scheduler) {
+        return registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+                scheduler, 0);
+    }
+
+    @Override
+    public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+            IntentFilter filter, @Nullable String broadcastPermission,
+            @Nullable Handler scheduler, int flags) {
+        BroadcastReceiverRegistration reg = new BroadcastReceiverRegistration(
+                receiver, user, filter, broadcastPermission, scheduler, flags
+        );
+        mRegistrationList.add(reg);
+        return null;
+    }
+
+    @Override
+    public void unregisterReceiver(BroadcastReceiver receiver) {
+        mRegistrationList.removeIf((reg) -> reg.mReceiver == receiver);
+    }
+
+    static class BroadcastReceiverRegistration {
+        final BroadcastReceiver mReceiver;
+        final UserHandle mUser;
+        final IntentFilter mIntentFilter;
+        final String mBroadcastPermission;
+        final Handler mHandler;
+        final int mFlags;
+
+        BroadcastReceiverRegistration(BroadcastReceiver receiver, UserHandle user,
+                IntentFilter intentFilter, String broadcastPermission, Handler handler, int flags) {
+            mReceiver = receiver;
+            mUser = user;
+            mIntentFilter = intentFilter;
+            mBroadcastPermission = broadcastPermission;
+            mHandler = handler;
+            mFlags = flags;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/OWNERS b/services/tests/servicestests/src/com/android/server/statusbar/OWNERS
new file mode 100644
index 0000000..a7e9194
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/statusbar/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
new file mode 100644
index 0000000..c293b5e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -0,0 +1,659 @@
+/*
+ * 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.statusbar;
+
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.quicksettings.TileService;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.IStatusBar;
+import com.android.server.LocalServices;
+import com.android.server.policy.GlobalActionsProvider;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class StatusBarManagerServiceTest {
+
+    private static final String TEST_PACKAGE = "test_pkg";
+    private static final String TEST_SERVICE = "test_svc";
+    private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+            TEST_SERVICE);
+    private static final CharSequence APP_NAME = "AppName";
+    private static final CharSequence TILE_LABEL = "Tile label";
+
+    @Rule
+    public final TestableContext mContext =
+            new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternal;
+    @Mock
+    private ApplicationInfo mApplicationInfo;
+    @Mock
+    private IStatusBar.Stub mMockStatusBar;
+    @Captor
+    private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor;
+
+    private Icon mIcon;
+    private StatusBarManagerService mStatusBarManagerService;
+
+    @BeforeClass
+    public static void oneTimeInitialization() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+
+        when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar);
+        when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME);
+
+        mStatusBarManagerService = new StatusBarManagerService(mContext);
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.removeServiceForTest(GlobalActionsProvider.class);
+
+        mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(
+                mStatusBarManagerService);
+
+        mStatusBarManagerService.registerStatusBar(mMockStatusBar);
+
+        mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus);
+    }
+
+    @Test
+    public void testHandleIncomingUserCalled() {
+        int fakeUser = 17;
+        try {
+            mStatusBarManagerService.requestAddTile(
+                    TEST_COMPONENT,
+                    TILE_LABEL,
+                    mIcon,
+                    fakeUser,
+                    new Callback()
+            );
+            fail("Should have SecurityException from uid check");
+        } catch (SecurityException e) {
+            verify(mActivityManagerInternal).handleIncomingUser(
+                    eq(Binder.getCallingPid()),
+                    eq(Binder.getCallingUid()),
+                    eq(fakeUser),
+                    eq(false),
+                    eq(ActivityManagerInternal.ALLOW_NON_FULL),
+                    anyString(),
+                    eq(TEST_PACKAGE)
+            );
+        }
+    }
+
+    @Test
+    public void testCheckUid_pass() {
+        when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+                .thenReturn(Binder.getCallingUid());
+        try {
+            mStatusBarManagerService.requestAddTile(
+                    TEST_COMPONENT,
+                    TILE_LABEL,
+                    mIcon,
+                    mContext.getUserId(),
+                    new Callback()
+            );
+        } catch (SecurityException e) {
+            fail("No SecurityException should be thrown");
+        }
+    }
+
+    @Test
+    public void testCheckUid_pass_differentUser() {
+        int otherUserUid = UserHandle.getUid(17, UserHandle.getAppId(Binder.getCallingUid()));
+        when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+                .thenReturn(otherUserUid);
+        try {
+            mStatusBarManagerService.requestAddTile(
+                    TEST_COMPONENT,
+                    TILE_LABEL,
+                    mIcon,
+                    mContext.getUserId(),
+                    new Callback()
+            );
+        } catch (SecurityException e) {
+            fail("No SecurityException should be thrown");
+        }
+    }
+
+    @Test
+    public void testCheckUid_fail() {
+        when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+                .thenReturn(Binder.getCallingUid() + 1);
+        try {
+            mStatusBarManagerService.requestAddTile(
+                    TEST_COMPONENT,
+                    TILE_LABEL,
+                    mIcon,
+                    mContext.getUserId(),
+                    new Callback()
+            );
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testCurrentUser_fail() {
+        mockUidCheck();
+        int user = 0;
+        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user + 1);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testCurrentUser_pass() {
+        mockUidCheck();
+        int user = 0;
+        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testValidComponent_fail_noComponentFound() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+        IntentMatcher im = new IntentMatcher(
+                new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+        when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+                eq(user), anyInt())).thenReturn(null);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+    }
+
+    @Test
+    public void testValidComponent_fail_notEnabled() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+
+        ResolveInfo r = makeResolveInfo();
+        r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+
+        IntentMatcher im = new IntentMatcher(
+                new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+        when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+                eq(user), anyInt())).thenReturn(r);
+        when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+                Binder.getCallingUid(), user)).thenReturn(
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+    }
+
+    @Test
+    public void testValidComponent_fail_noPermission() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+
+        ResolveInfo r = makeResolveInfo();
+
+        IntentMatcher im = new IntentMatcher(
+                new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+        when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+                eq(user), anyInt())).thenReturn(r);
+        when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+                Binder.getCallingUid(), user)).thenReturn(
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+    }
+
+    @Test
+    public void testValidComponent_fail_notExported() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+
+        ResolveInfo r = makeResolveInfo();
+        r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+        r.serviceInfo.exported = false;
+
+        IntentMatcher im = new IntentMatcher(
+                new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+        when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+                eq(user), anyInt())).thenReturn(r);
+        when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+                Binder.getCallingUid(), user)).thenReturn(
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+    }
+
+    @Test
+    public void testValidComponent_pass() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+
+        ResolveInfo r = makeResolveInfo();
+        r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+        r.serviceInfo.exported = true;
+
+        IntentMatcher im = new IntentMatcher(
+                new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+        when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+                eq(user), anyInt())).thenReturn(r);
+        when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+                Binder.getCallingUid(), user)).thenReturn(
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testAppInForeground_fail() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+        mockComponentInfo(user);
+
+        when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+                PROCESS_STATE_FOREGROUND_SERVICE);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testAppInForeground_pass() {
+        int user = 10;
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+        mockComponentInfo(user);
+
+        when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+                PROCESS_STATE_TOP);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testRequestToStatusBar() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+                new Callback());
+
+        verify(mMockStatusBar).requestAddTile(
+                eq(TEST_COMPONENT),
+                eq(APP_NAME),
+                eq(TILE_LABEL),
+                eq(mIcon),
+                any()
+        );
+    }
+
+    @Test
+    public void testRequestInProgress_samePackage() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+                new Callback());
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testRequestInProgress_differentPackage() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+        ComponentName otherComponent = new ComponentName("a", "b");
+        mockUidCheck(otherComponent.getPackageName());
+        mockComponentInfo(user, otherComponent);
+
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+                new Callback());
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(otherComponent, TILE_LABEL, mIcon, user, callback);
+
+        assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testResponseForwardedToCallback_tileAdded() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        verify(mMockStatusBar).requestAddTile(
+                eq(TEST_COMPONENT),
+                eq(APP_NAME),
+                eq(TILE_LABEL),
+                eq(mIcon),
+                mAddTileResultCallbackCaptor.capture()
+        );
+
+        mAddTileResultCallbackCaptor.getValue().onTileRequest(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED);
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, callback.mUserResponse);
+    }
+
+    @Test
+    public void testResponseForwardedToCallback_tileNotAdded() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        verify(mMockStatusBar).requestAddTile(
+                eq(TEST_COMPONENT),
+                eq(APP_NAME),
+                eq(TILE_LABEL),
+                eq(mIcon),
+                mAddTileResultCallbackCaptor.capture()
+        );
+
+        mAddTileResultCallbackCaptor.getValue().onTileRequest(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testResponseForwardedToCallback_tileAlreadyAdded() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        verify(mMockStatusBar).requestAddTile(
+                eq(TEST_COMPONENT),
+                eq(APP_NAME),
+                eq(TILE_LABEL),
+                eq(mIcon),
+                mAddTileResultCallbackCaptor.capture()
+        );
+
+        mAddTileResultCallbackCaptor.getValue().onTileRequest(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED);
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testResponseForwardedToCallback_dialogDismissed() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        verify(mMockStatusBar).requestAddTile(
+                eq(TEST_COMPONENT),
+                eq(APP_NAME),
+                eq(TILE_LABEL),
+                eq(mIcon),
+                mAddTileResultCallbackCaptor.capture()
+        );
+
+        mAddTileResultCallbackCaptor.getValue().onTileRequest(
+                StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
+        // This gets translated to TILE_NOT_ADDED
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testInstaDenialAfterManyDenials() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+                    new Callback());
+
+            verify(mMockStatusBar, times(i + 1)).requestAddTile(
+                    eq(TEST_COMPONENT),
+                    eq(APP_NAME),
+                    eq(TILE_LABEL),
+                    eq(mIcon),
+                    mAddTileResultCallbackCaptor.capture()
+            );
+            mAddTileResultCallbackCaptor.getValue().onTileRequest(
+                    StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+        }
+
+        Callback callback = new Callback();
+        mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+        // Only called MAX_NUM_DENIALS times
+        verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
+                any(),
+                any(),
+                any(),
+                any(),
+                mAddTileResultCallbackCaptor.capture()
+        );
+        assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+                callback.mUserResponse);
+    }
+
+    @Test
+    public void testDialogDismissalNotCountingAgainstDenials() throws RemoteException {
+        int user = 10;
+        mockEverything(user);
+
+        for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS * 2; i++) {
+            mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+                    new Callback());
+
+            verify(mMockStatusBar, times(i + 1)).requestAddTile(
+                    eq(TEST_COMPONENT),
+                    eq(APP_NAME),
+                    eq(TILE_LABEL),
+                    eq(mIcon),
+                    mAddTileResultCallbackCaptor.capture()
+            );
+            mAddTileResultCallbackCaptor.getValue().onTileRequest(
+                    StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
+        }
+    }
+
+    private void mockUidCheck() {
+        mockUidCheck(TEST_PACKAGE);
+    }
+
+    private void mockUidCheck(String packageName) {
+        when(mPackageManagerInternal.getPackageUid(eq(packageName), anyInt(), anyInt()))
+                .thenReturn(Binder.getCallingUid());
+    }
+
+    private void mockCurrentUserCheck(int user) {
+        when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
+    }
+
+    private void mockComponentInfo(int user) {
+        mockComponentInfo(user, TEST_COMPONENT);
+    }
+
+    private ResolveInfo makeResolveInfo() {
+        ResolveInfo r = new ResolveInfo();
+        r.serviceInfo = new ServiceInfo();
+        r.serviceInfo.applicationInfo = mApplicationInfo;
+        return r;
+    }
+
+    private void mockComponentInfo(int user, ComponentName componentName) {
+        ResolveInfo r = makeResolveInfo();
+        r.serviceInfo.exported = true;
+        r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+
+        IntentMatcher im = new IntentMatcher(
+                new Intent(TileService.ACTION_QS_TILE).setComponent(componentName));
+        when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+                eq(user), anyInt())).thenReturn(r);
+        when(mPackageManagerInternal.getComponentEnabledSetting(componentName,
+                Binder.getCallingUid(), user)).thenReturn(
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+    }
+
+    private void mockProcessState() {
+        when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+                PROCESS_STATE_TOP);
+    }
+
+    private void mockEverything(int user) {
+        mockUidCheck();
+        mockCurrentUserCheck(user);
+        mockComponentInfo(user);
+        mockProcessState();
+    }
+
+    private static class Callback extends IAddTileResultCallback.Stub {
+        int mUserResponse = -1;
+
+        @Override
+        public void onTileRequest(int userResponse) throws RemoteException {
+            if (mUserResponse != -1) {
+                throw new IllegalStateException(
+                        "Setting response to " + userResponse + " but it already has "
+                                + mUserResponse);
+            }
+            mUserResponse = userResponse;
+        }
+    }
+
+    private static class IntentMatcher implements ArgumentMatcher<Intent> {
+        private final Intent mIntent;
+
+        IntentMatcher(Intent intent) {
+            mIntent = intent;
+        }
+
+        @Override
+        public boolean matches(Intent argument) {
+            return argument != null && argument.filterEquals(mIntent);
+        }
+
+        @Override
+        public String toString() {
+            return "Expected: " + mIntent;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java b/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java
new file mode 100644
index 0000000..dac6df9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.statusbar;
+
+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 android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class TileRequestTrackerTest {
+
+    private static final String TEST_PACKAGE = "test_pkg";
+    private static final String TEST_SERVICE = "test_svc";
+    private static final String TEST_SERVICE_OTHER = "test_svc_other";
+    private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+            TEST_SERVICE);
+    private static final ComponentName TEST_COMPONENT_OTHER = new ComponentName(TEST_PACKAGE,
+            TEST_SERVICE_OTHER);
+    private static final ComponentName TEST_COMPONENT_OTHER_PACKAGE = new ComponentName("other",
+            TEST_SERVICE);
+    private static final int USER_ID = 0;
+    private static final int USER_ID_OTHER = 10;
+    private static final int APP_UID = 12345;
+    private static final int USER_UID = UserHandle.getUid(USER_ID, APP_UID);
+    private static final int USER_OTHER_UID = UserHandle.getUid(USER_ID_OTHER, APP_UID);
+
+    @Rule
+    public final NoBroadcastContextWrapper mContext =
+            new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+
+    private TileRequestTracker mTileRequestTracker;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTileRequestTracker = new TileRequestTracker(mContext);
+    }
+
+    @Test
+    public void testBroadcastReceiverRegistered() {
+        NoBroadcastContextWrapper.BroadcastReceiverRegistration reg = getReceiverRegistration();
+
+        assertEquals(UserHandle.ALL, reg.mUser);
+        assertNull(reg.mBroadcastPermission);
+        assertNotNull(reg.mReceiver);
+
+        IntentFilter filter = reg.mIntentFilter;
+        assertEquals(2, filter.countActions());
+        assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
+        assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_DATA_CLEARED));
+        assertTrue(filter.hasDataScheme("package"));
+    }
+
+    @Test
+    public void testNoDenialsFromStart() {
+        // Certainly not an exhaustive test
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT_OTHER));
+    }
+
+    @Test
+    public void testNoDenialBeforeMax() {
+        for (int i = 1; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+        }
+
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+    }
+
+    @Test
+    public void testDenialOnMax() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+        }
+        assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+    }
+
+    @Test
+    public void testDenialPerUser() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+        }
+
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+    }
+
+    @Test
+    public void testDenialPerComponent() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+        }
+
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+    }
+
+    @Test
+    public void testPackageUninstallRemovesDenials_allComponents() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT_OTHER);
+        }
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.putExtra(Intent.EXTRA_UID, USER_UID);
+        intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+        getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+    }
+
+    @Test
+    public void testPackageUninstallRemoveDenials_differentUsers() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+            mTileRequestTracker.addDenial(USER_ID_OTHER, TEST_COMPONENT);
+        }
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.putExtra(Intent.EXTRA_UID, USER_OTHER_UID);
+        intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+        getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+        // User 0 package was not removed
+        assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+        // User 10 package was removed
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+    }
+
+    @Test
+    public void testPackageUninstallRemoveDenials_differentPackages() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT_OTHER_PACKAGE);
+        }
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.putExtra(Intent.EXTRA_UID, USER_UID);
+        intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+        getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+        // Package TEST_PACKAGE removed
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+        // Package "other" not removed
+        assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER_PACKAGE));
+    }
+
+    @Test
+    public void testPackageUpdateDoesntRemoveDenials() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+        }
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.putExtra(Intent.EXTRA_REPLACING, true);
+        intent.putExtra(Intent.EXTRA_UID, USER_UID);
+        intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+        getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+        assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+    }
+
+    @Test
+    public void testClearPackageDataRemovesDenials() {
+        for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+            mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+        }
+
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
+        intent.putExtra(Intent.EXTRA_UID, USER_UID);
+        intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+        getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+        assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+    }
+
+    private NoBroadcastContextWrapper.BroadcastReceiverRegistration getReceiverRegistration() {
+        assertEquals(1, mContext.mRegistrationList.size());
+        return mContext.mRegistrationList.get(0);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 4dcd633..add4cda 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -62,12 +62,15 @@
     private static final String LOG_TAG = "SystemConfigTest";
 
     private SystemConfig mSysConfig;
+    private File mFooJar;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     @Before
     public void setUp() throws Exception {
         mSysConfig = new SystemConfigTestClass();
+        mFooJar = createTempFile(
+                mTemporaryFolder.getRoot().getCanonicalFile(), "foo.jar", "JAR");
     }
 
     /**
@@ -340,7 +343,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        on-bootclasspath-before=\"10\"\n"
                 + "        on-bootclasspath-since=\"20\"\n"
                 + "     />\n\n"
@@ -362,7 +365,7 @@
                 "<permissions>\n"
                         + "    <updatable-library \n"
                         + "        name=\"foo\"\n"
-                        + "        file=\"foo.jar\"\n"
+                        + "        file=\"" + mFooJar + "\"\n"
                         + "        on-bootclasspath-before=\"10\"\n"
                         + "        on-bootclasspath-since=\"20\"\n"
                         + "     />\n\n"
@@ -384,7 +387,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        min-device-sdk=\"30\"\n"
                 + "     />\n\n"
                 + " </permissions>";
@@ -402,7 +405,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        min-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
                 + "     />\n\n"
                 + " </permissions>";
@@ -420,7 +423,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        min-device-sdk=\"" + (Build.VERSION.SDK_INT + 1) + "\"\n"
                 + "     />\n\n"
                 + " </permissions>";
@@ -438,7 +441,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        max-device-sdk=\"30\"\n"
                 + "     />\n\n"
                 + " </permissions>";
@@ -456,7 +459,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        max-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
                 + "     />\n\n"
                 + " </permissions>";
@@ -474,7 +477,7 @@
                 "<permissions>\n"
                 + "    <library \n"
                 + "        name=\"foo\"\n"
-                + "        file=\"foo.jar\"\n"
+                + "        file=\"" + mFooJar + "\"\n"
                 + "        max-device-sdk=\"" + (Build.VERSION.SDK_INT + 1) + "\"\n"
                 + "     />\n\n"
                 + " </permissions>";
@@ -507,7 +510,7 @@
      * @param folder   pre-existing subdirectory of mTemporaryFolder to put the file
      * @param fileName name of the file (e.g. filename.xml) to create
      * @param contents contents to write to the file
-     * @return the folder containing the newly created file (not the file itself!)
+     * @return the newly created file
      */
     private File createTempFile(File folder, String fileName, String contents)
             throws IOException {
@@ -523,13 +526,13 @@
             Log.d(LOG_TAG, input.nextLine());
         }
 
-        return folder;
+        return file;
     }
 
     private void assertFooIsOnlySharedLibrary() {
         assertThat(mSysConfig.getSharedLibraries().size()).isEqualTo(1);
         SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
         assertThat(entry.name).isEqualTo("foo");
-        assertThat(entry.filename).isEqualTo("foo.jar");
+        assertThat(entry.filename).isEqualTo(mFooJar.toString());
     }
 }
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 6ee6020c..767c466 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -21,6 +21,10 @@
 import static android.app.time.Capabilities.CAPABILITY_NOT_SUPPORTED;
 import static android.app.time.Capabilities.CAPABILITY_POSSESSED;
 
+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 org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -48,7 +52,9 @@
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
+                .setGeoDetectionRunInBackgroundEnabled(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -60,7 +66,8 @@
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+            assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOnConfig.createCapabilitiesAndConfig();
@@ -85,7 +92,8 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOffConfig.createCapabilitiesAndConfig();
@@ -112,6 +120,8 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setGeoDetectionRunInBackgroundEnabled(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -123,7 +133,8 @@
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertTrue(autoOnConfig.getGeoDetectionEnabledBehavior());
+            assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOnConfig.createCapabilitiesAndConfig();
@@ -149,7 +160,8 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOffConfig.createCapabilitiesAndConfig();
@@ -176,7 +188,9 @@
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
+                .setGeoDetectionRunInBackgroundEnabled(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -188,7 +202,8 @@
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
             assertFalse(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+            assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOnConfig.createCapabilitiesAndConfig();
@@ -211,7 +226,8 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOffConfig.createCapabilitiesAndConfig();
@@ -239,7 +255,9 @@
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
+                .setGeoDetectionRunInBackgroundEnabled(false)
                 .setTelephonyFallbackSupported(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(true)
@@ -251,7 +269,8 @@
             assertTrue(autoOnConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getGeoDetectionEnabledSetting());
             assertTrue(autoOnConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOnConfig.getGeoDetectionEnabledBehavior());
+            assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOnConfig.createCapabilitiesAndConfig();
@@ -275,7 +294,8 @@
             assertFalse(autoOffConfig.getAutoDetectionEnabledSetting());
             assertTrue(autoOffConfig.getGeoDetectionEnabledSetting());
             assertFalse(autoOffConfig.getAutoDetectionEnabledBehavior());
-            assertFalse(autoOffConfig.getGeoDetectionEnabledBehavior());
+            assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
             TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
                     autoOffConfig.createCapabilitiesAndConfig();
@@ -306,4 +326,66 @@
                 .build();
         assertTrue(config.isTelephonyFallbackSupported());
     }
+
+    /** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
+    @Test
+    public void test_geoDetectionRunInBackgroundEnabled() {
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setTelephonyDetectionFeatureSupported(true)
+                .setGeoDetectionFeatureSupported(true)
+                .setGeoDetectionRunInBackgroundEnabled(true)
+                .setEnhancedMetricsCollectionEnabled(false)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        {
+            ConfigurationInternal config = baseConfig;
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_GEO, config.getDetectionMode());
+        }
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setGeoDetectionFeatureSupported(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
+        }
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setTelephonyDetectionFeatureSupported(false)
+                    .setGeoDetectionFeatureSupported(false)
+                    .build();
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
+        }
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setGeoDetectionEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
+        }
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setLocationEnabledSetting(false)
+                    .build();
+            assertTrue(config.getAutoDetectionEnabledBehavior());
+            assertFalse(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_TELEPHONY, config.getDetectionMode());
+        }
+        {
+            ConfigurationInternal config = new ConfigurationInternal.Builder(baseConfig)
+                    .setAutoDetectionEnabledSetting(false)
+                    .build();
+            assertFalse(config.getAutoDetectionEnabledBehavior());
+            assertTrue(config.isGeoDetectionExecutionEnabled());
+            assertEquals(DETECTION_MODE_MANUAL, config.getDetectionMode());
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
new file mode 100644
index 0000000..782eebf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.timezonedetector;
+
+import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.UserIdInt;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
+
+import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsTimeZoneSuggestion;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.function.Function;
+
+/** Tests for {@link MetricsTimeZoneDetectorState}. */
+public class MetricsTimeZoneDetectorStateTest {
+
+    private static final @UserIdInt int ARBITRARY_USER_ID = 1;
+    private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+    private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
+
+    private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
+            new ManualTimeZoneSuggestion("ManualTimeZoneId");
+
+    private static final TelephonyTimeZoneSuggestion TELEPHONY_TIME_ZONE_SUGGESTION =
+            new TelephonyTimeZoneSuggestion.Builder(0)
+                    .setZoneId("TelephonyZoneId")
+                    .setMatchType(TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY)
+                    .setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                    .build();
+
+    private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+            GeolocationTimeZoneSuggestion.createCertainSuggestion(
+                    ARBITRARY_ELAPSED_REALTIME_MILLIS,
+                    Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+
+    private final OrdinalGenerator<String> mOrdinalGenerator =
+            new OrdinalGenerator<>(Function.identity());
+
+    @Test
+    public void enhancedMetricsCollectionEnabled() {
+        final boolean enhancedMetricsCollectionEnabled = true;
+        ConfigurationInternal configurationInternal =
+                createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+        // Create the object.
+        MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+                MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+                        DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+        // Assert the content.
+        assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+        assertEquals(DEVICE_TIME_ZONE_ID, metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+        MetricsTimeZoneSuggestion expectedManualSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        new String[] { MANUAL_TIME_ZONE_SUGGESTION.getZoneId() },
+                        new int[] { 1 });
+        assertEquals(expectedManualSuggestion,
+                metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+        MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        new String[] { TELEPHONY_TIME_ZONE_SUGGESTION.getZoneId() },
+                        new int[] { 2 });
+        assertEquals(expectedTelephonySuggestion,
+                metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+        MetricsTimeZoneSuggestion expectedGeoSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+                        new int[] { 3, 4 });
+        assertEquals(expectedGeoSuggestion,
+                metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+    }
+
+    @Test
+    public void enhancedMetricsCollectionDisabled() {
+        final boolean enhancedMetricsCollectionEnabled = false;
+        ConfigurationInternal configurationInternal =
+                createConfigurationInternal(enhancedMetricsCollectionEnabled);
+
+        // Create the object.
+        MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
+                MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
+                        DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
+                        TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+
+        // Assert the content.
+        assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
+
+        // When enhancedMetricsCollectionEnabled == false, no time zone IDs should be included.
+        assertNull(metricsTimeZoneDetectorState.getDeviceTimeZoneId());
+        final String[] omittedZoneIds = null;
+
+        MetricsTimeZoneSuggestion expectedManualSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 1 });
+        assertEquals(expectedManualSuggestion,
+                metricsTimeZoneDetectorState.getLatestManualSuggestion());
+
+        MetricsTimeZoneSuggestion expectedTelephonySuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 2 });
+        assertEquals(expectedTelephonySuggestion,
+                metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+
+        MetricsTimeZoneSuggestion expectedGeoSuggestion =
+                MetricsTimeZoneSuggestion.createCertain(
+                        omittedZoneIds,
+                        new int[] { 3, 4 });
+        assertEquals(expectedGeoSuggestion,
+                metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
+    }
+
+    private static void assertCommonConfiguration(ConfigurationInternal configurationInternal,
+            MetricsTimeZoneDetectorState metricsTimeZoneDetectorState) {
+        assertEquals(configurationInternal.isTelephonyDetectionSupported(),
+                metricsTimeZoneDetectorState.isTelephonyDetectionSupported());
+        assertEquals(configurationInternal.isGeoDetectionSupported(),
+                metricsTimeZoneDetectorState.isGeoDetectionSupported());
+        assertEquals(configurationInternal.isTelephonyFallbackSupported(),
+                metricsTimeZoneDetectorState.isTelephonyTimeZoneFallbackSupported());
+        assertEquals(configurationInternal.getGeoDetectionRunInBackgroundEnabled(),
+                metricsTimeZoneDetectorState.getGeoDetectionRunInBackgroundEnabled());
+        assertEquals(configurationInternal.isEnhancedMetricsCollectionEnabled(),
+                metricsTimeZoneDetectorState.isEnhancedMetricsCollectionEnabled());
+        assertEquals(configurationInternal.getAutoDetectionEnabledSetting(),
+                metricsTimeZoneDetectorState.getAutoDetectionEnabledSetting());
+        assertEquals(configurationInternal.getLocationEnabledSetting(),
+                metricsTimeZoneDetectorState.getUserLocationEnabledSetting());
+        assertEquals(configurationInternal.getGeoDetectionEnabledSetting(),
+                metricsTimeZoneDetectorState.getGeoDetectionEnabledSetting());
+        assertEquals(0, metricsTimeZoneDetectorState.getDeviceTimeZoneIdOrdinal());
+        assertEquals(DETECTION_MODE_GEO, metricsTimeZoneDetectorState.getDetectionMode());
+    }
+
+    private static ConfigurationInternal createConfigurationInternal(
+            boolean enhancedMetricsCollectionEnabled) {
+        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+                .setUserConfigAllowed(true)
+                .setTelephonyDetectionFeatureSupported(true)
+                .setGeoDetectionFeatureSupported(true)
+                .setTelephonyFallbackSupported(false)
+                .setGeoDetectionRunInBackgroundEnabled(false)
+                .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollectionEnabled)
+                .setAutoDetectionEnabledSetting(true)
+                .setLocationEnabledSetting(true)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+    }
+}
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 193b2e3..6365b98 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -372,13 +372,15 @@
     }
 
     private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
-        // Default geo detection settings from the auto detection setting - they are not important
-        // to the tests.
+        // 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)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setGeoDetectionRunInBackgroundEnabled(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setUserConfigAllowed(true)
                 .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                 .setLocationEnabledSetting(geoDetectionEnabled)
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 ef1b4f5..23a9013 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -87,10 +87,12 @@
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -98,10 +100,12 @@
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(false)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(false)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(true)
@@ -109,10 +113,12 @@
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
                     .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -120,10 +126,12 @@
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
             new ConfigurationInternal.Builder(USER_ID)
-                    .setUserConfigAllowed(true)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
+                    .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(false)
                     .setLocationEnabledSetting(true)
                     .setGeoDetectionEnabledSetting(false)
@@ -134,6 +142,8 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
@@ -145,6 +155,8 @@
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
+                    .setGeoDetectionRunInBackgroundEnabled(false)
+                    .setEnhancedMetricsCollectionEnabled(false)
                     .setUserConfigAllowed(true)
                     .setAutoDetectionEnabledSetting(true)
                     .setLocationEnabledSetting(true)
@@ -955,8 +967,20 @@
     }
 
     @Test
-    public void testGenerateMetricsState() {
-        ConfigurationInternal expectedInternalConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+    public void testGenerateMetricsState_enhancedMetricsCollection() {
+        testGenerateMetricsState(true);
+    }
+
+    @Test
+    public void testGenerateMetricsState_notEnhancedMetricsCollection() {
+        testGenerateMetricsState(false);
+    }
+
+    private void testGenerateMetricsState(boolean enhancedMetricsCollection) {
+        ConfigurationInternal expectedInternalConfig =
+                new ConfigurationInternal.Builder(CONFIG_AUTO_DISABLED_GEO_DISABLED)
+                        .setEnhancedMetricsCollectionEnabled(enhancedMetricsCollection)
+                        .build();
         String expectedDeviceTimeZoneId = "InitialZoneId";
 
         Script script = new Script()
@@ -1028,7 +1052,7 @@
                         tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
                         expectedManualSuggestion, expectedTelephonySuggestion,
                         expectedGeolocationTimeZoneSuggestion);
-        // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID ordinal comparisons.
+        // Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
         assertEquals(expectedState, actualState);
     }
 
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 d54e1f1..0257ce0 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,6 +15,7 @@
  */
 package com.android.server.timezonedetector.location;
 
+import static com.android.server.timezonedetector.ConfigurationInternal.DETECTION_MODE_MANUAL;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_DESTROYED;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_PERM_FAILED;
 import static com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.PROVIDER_STATE_STARTED_CERTAIN;
@@ -1333,6 +1334,53 @@
         assertFalse(controller.isUncertaintyTimeoutSet());
     }
 
+    /**
+     * A controller-state-only test to prove that "run in background" configuration behaves as
+     * intended. Provider states are well covered by other "enabled" tests.
+     */
+    @Test
+    public void geoDetectionRunInBackground() {
+        LocationTimeZoneProviderController controller = new LocationTimeZoneProviderController(
+                mTestThreadingDomain, mTestMetricsLogger, mTestPrimaryLocationTimeZoneProvider,
+                mTestSecondaryLocationTimeZoneProvider, false /* recordStateChanges */);
+
+        // A configuration where the user has geo-detection disabled.
+        ConfigurationInternal runInBackgroundDisabledConfig =
+                new ConfigurationInternal.Builder(USER1_CONFIG_GEO_DETECTION_DISABLED)
+                        .setLocationEnabledSetting(true)
+                        .setAutoDetectionEnabledSetting(false)
+                        .setGeoDetectionEnabledSetting(false)
+                        .setGeoDetectionRunInBackgroundEnabled(false)
+                        .build();
+        // A configuration where geo-detection is disabled by the user but can run in the
+        // background.
+        ConfigurationInternal runInBackgroundEnabledConfig =
+                new ConfigurationInternal.Builder(runInBackgroundDisabledConfig)
+                        .setGeoDetectionRunInBackgroundEnabled(true)
+                        .build();
+        assertEquals(DETECTION_MODE_MANUAL, runInBackgroundEnabledConfig.getDetectionMode());
+        assertTrue(runInBackgroundEnabledConfig.isGeoDetectionExecutionEnabled());
+
+        TestEnvironment testEnvironment = new TestEnvironment(
+                mTestThreadingDomain, controller, runInBackgroundDisabledConfig);
+
+        // Initialize and check initial state.
+        controller.initialize(testEnvironment, mTestCallback);
+
+        assertControllerState(controller, STATE_STOPPED);
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
+
+        testEnvironment.simulateConfigChange(runInBackgroundEnabledConfig);
+
+        assertControllerState(controller, STATE_INITIALIZING);
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
+
+        testEnvironment.simulateConfigChange(runInBackgroundDisabledConfig);
+
+        assertControllerState(controller, STATE_STOPPED);
+        mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
+    }
+
     private static void assertUncertaintyTimeoutSet(
             LocationTimeZoneProviderController.Environment environment,
             LocationTimeZoneProviderController controller) {
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 a2df3130..2c3a7c4 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
@@ -47,6 +47,8 @@
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
+                .setGeoDetectionRunInBackgroundEnabled(false)
+                .setEnhancedMetricsCollectionEnabled(false)
                 .setAutoDetectionEnabledSetting(true)
                 .setLocationEnabledSetting(true)
                 .setGeoDetectionEnabledSetting(geoDetectionEnabledSetting)
diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
index 299b9a0..70fd28d 100644
--- a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
+++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp4.xml
@@ -57,6 +57,11 @@
 	        <property android:name="android.cts.PROPERTY_SERVICE" android:value="@integer/integer_property" />
 	        <property android:name="android.cts.PROPERTY_COMPONENT" android:resource="@integer/integer_property" />
 	    </service>
+		<apex-system-service
+			android:name="com.android.servicestests.apps.packageparserapp.SystemService"
+			android:path="service-test.jar"
+			android:minSdkVersion = "30"
+			android:maxSdkVersion = "31" />
     </application>
 
     <instrumentation
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 837850f..62a0dd4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -63,10 +63,6 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 
-import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS;
-import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS;
-import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -340,7 +336,6 @@
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
-    BroadcastReceiver mNASIntentReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
@@ -500,14 +495,9 @@
                     && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED)
                     && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 mPackageIntentReceiver = broadcastReceivers.get(i);
-            } else if (filter.hasAction(ACTION_ENABLE_NAS)
-                    && filter.hasAction(ACTION_DISABLE_NAS)
-                    && filter.hasAction(ACTION_LEARNMORE_NAS)) {
-                mNASIntentReceiver = broadcastReceivers.get(i);
             }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
-        assertNotNull("nas intent receiver should exist", mNASIntentReceiver);
 
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
@@ -602,16 +592,6 @@
         mPackageIntentReceiver.onReceive(getContext(), intent);
     }
 
-    private void simulateNASUpgradeBroadcast(String action, int uid) {
-        final Bundle extras = new Bundle();
-        extras.putInt(Intent.EXTRA_USER_ID, uid);
-
-        final Intent intent = new Intent(action);
-        intent.putExtras(extras);
-
-        mNASIntentReceiver.onReceive(getContext(), intent);
-    }
-
     private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
         ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
         changed.put(true, new ArrayList<>());
@@ -6007,7 +5987,7 @@
     }
 
     @Test
-    public void testNASSettingUpgrade_userSetNull_noOnBoarding() throws RemoteException {
+    public void testNASSettingUpgrade_userSetNull() throws RemoteException {
         ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component1");
         TestableNotificationManagerService service = spy(mService);
         int userId = 11;
@@ -6020,14 +6000,13 @@
                 .thenReturn(new ArrayList<>());
         when(mAssistants.hasUserSet(userId)).thenReturn(true);
 
-        service.migrateDefaultNASShowNotificationIfNecessary();
+        service.migrateDefaultNAS();
         assertTrue(service.isNASMigrationDone(userId));
-        verify(service, times(0)).createNASUpgradeNotification(eq(userId));
         verify(mAssistants, times(1)).clearDefaults();
     }
 
     @Test
-    public void testNASSettingUpgrade_userSetSameDefault_noOnBoarding() throws RemoteException {
+    public void testNASSettingUpgrade_userSet() throws RemoteException {
         ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component1");
         TestableNotificationManagerService service = spy(mService);
         int userId = 11;
@@ -6040,55 +6019,10 @@
                 .thenReturn(new ArrayList(Arrays.asList(defaultComponent)));
         when(mAssistants.hasUserSet(userId)).thenReturn(true);
 
-        service.migrateDefaultNASShowNotificationIfNecessary();
-        assertTrue(service.isNASMigrationDone(userId));
-        verify(service, times(0)).createNASUpgradeNotification(eq(userId));
-        verify(mAssistants, times(1)).resetDefaultFromConfig();
-    }
-
-    @Test
-    public void testNASSettingUpgrade_userSetDifferentDefault_showOnboarding()
-            throws RemoteException {
-        ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1");
-        ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2");
-        TestableNotificationManagerService service = spy(mService);
-        int userId = 11;
-        setUsers(new int[]{userId});
-        setNASMigrationDone(false, userId);
-        when(mAssistants.getDefaultComponents())
-                .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent)));
-        when(mAssistants.getDefaultFromConfig())
-                .thenReturn(newDefaultComponent);
-        when(mAssistants.getAllowedComponents(anyInt()))
-                .thenReturn(Arrays.asList(oldDefaultComponent));
-        when(mAssistants.hasUserSet(userId)).thenReturn(true);
-
-        service.migrateDefaultNASShowNotificationIfNecessary();
-        assertFalse(service.isNASMigrationDone(userId));
-        //TODO(b/192450820)
-        //verify(service, times(1)).createNASUpgradeNotification(eq(userId));
-        verify(mAssistants, times(0)).resetDefaultFromConfig();
-
-        //Test user clear data before enable/disable from onboarding notification
-        ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners =
-                generateResetComponentValues();
-        when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners);
-        ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>();
-        changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent)));
-        changes.put(false, new ArrayList());
-        when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes);
-
-        //Clear data
-        service.getBinderService().clearData("package", userId, false);
-        //Test migrate flow again
-        service.migrateDefaultNASShowNotificationIfNecessary();
-
-        //The notification should be still there
-        assertFalse(service.isNASMigrationDone(userId));
-        //TODO(b/192450820)
-        //verify(service, times(2)).createNASUpgradeNotification(eq(userId));
-        verify(mAssistants, times(0)).resetDefaultFromConfig();
-        assertEquals(oldDefaultComponent, service.getApprovedAssistant(userId));
+        service.migrateDefaultNAS();
+        verify(mAssistants, times(1)).setUserSet(userId, false);
+        //resetDefaultAssistantsIfNecessary should invoke from readPolicyXml() and migration
+        verify(mAssistants, times(2)).resetDefaultAssistantsIfNecessary();
     }
 
     @Test
@@ -6108,24 +6042,22 @@
                 .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent)));
         when(mAssistants.getDefaultFromConfig())
                 .thenReturn(newDefaultComponent);
-        //User1: need onboarding
+        //User1: set different NAS
         when(mAssistants.getAllowedComponents(userId1))
                 .thenReturn(Arrays.asList(oldDefaultComponent));
-        //User2: no onboarding
+        //User2: set to none
         when(mAssistants.getAllowedComponents(userId2))
-                .thenReturn(Arrays.asList(newDefaultComponent));
+                .thenReturn(new ArrayList<>());
 
         when(mAssistants.hasUserSet(userId1)).thenReturn(true);
         when(mAssistants.hasUserSet(userId2)).thenReturn(true);
 
-        service.migrateDefaultNASShowNotificationIfNecessary();
-        assertFalse(service.isNASMigrationDone(userId1));
+        service.migrateDefaultNAS();
+        // user1's setting get reset
+        verify(mAssistants, times(1)).setUserSet(userId1, false);
+        verify(mAssistants, times(0)).setUserSet(eq(userId2), anyBoolean());
         assertTrue(service.isNASMigrationDone(userId2));
 
-        //TODO(b/192450820)
-        //verify(service, times(1)).createNASUpgradeNotification(any(Integer.class));
-        // only user2's default get updated
-        verify(mAssistants, times(1)).resetDefaultFromConfig();
     }
 
     @Test
@@ -6145,7 +6077,7 @@
                 .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent)));
         when(mAssistants.getDefaultFromConfig())
                 .thenReturn(newDefaultComponent);
-        //Both profiles: need onboarding
+        //Both profiles: set different NAS
         when(mAssistants.getAllowedComponents(userId1))
                 .thenReturn(Arrays.asList(oldDefaultComponent));
         when(mAssistants.getAllowedComponents(userId2))
@@ -6154,13 +6086,9 @@
         when(mAssistants.hasUserSet(userId1)).thenReturn(true);
         when(mAssistants.hasUserSet(userId2)).thenReturn(true);
 
-        service.migrateDefaultNASShowNotificationIfNecessary();
+        service.migrateDefaultNAS();
         assertFalse(service.isNASMigrationDone(userId1));
         assertFalse(service.isNASMigrationDone(userId2));
-
-        // TODO(b/192450820): only user1 get notification
-        //verify(service, times(1)).createNASUpgradeNotification(eq(userId1));
-        //verify(service, times(0)).createNASUpgradeNotification(eq(userId2));
     }
 
 
@@ -6188,79 +6116,16 @@
         //Clear data
         service.getBinderService().clearData("package", userId, false);
         //Test migrate flow again
-        service.migrateDefaultNASShowNotificationIfNecessary();
+        service.migrateDefaultNAS();
 
-        //TODO(b/192450820): The notification should not appear again
-        //verify(service, times(0)).createNASUpgradeNotification(eq(userId));
-        verify(mAssistants, times(0)).resetDefaultFromConfig();
+        //Migration should not happen again
+        verify(mAssistants, times(0)).setUserSet(userId, false);
+        verify(mAssistants, times(0)).clearDefaults();
+        //resetDefaultAssistantsIfNecessary should only invoke once from readPolicyXml()
+        verify(mAssistants, times(1)).resetDefaultAssistantsIfNecessary();
+
     }
 
-    @Test
-    public void testNASUpgradeNotificationDisableBroadcast_multiProfile() {
-        int userId1 = 11;
-        int userId2 = 12;
-        setUsers(new int[]{userId1, userId2});
-        when(mUm.isManagedProfile(userId2)).thenReturn(true);
-        when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2});
-
-        TestableNotificationManagerService service = spy(mService);
-        setNASMigrationDone(false, userId1);
-        setNASMigrationDone(false, userId2);
-
-        simulateNASUpgradeBroadcast(ACTION_DISABLE_NAS, userId1);
-
-        assertTrue(service.isNASMigrationDone(userId1));
-        assertTrue(service.isNASMigrationDone(userId2));
-        // User disabled the NAS from notification, the default stored in xml should be null
-        // rather than the new default
-        verify(mAssistants, times(1)).clearDefaults();
-        verify(mAssistants, times(0)).resetDefaultFromConfig();
-
-        //TODO(b/192450820):No more notification after disabled
-        //service.migrateDefaultNASShowNotificationIfNecessary();
-        //verify(service, times(0)).createNASUpgradeNotification(anyInt());
-    }
-
-    @Test
-    public void testNASUpgradeNotificationEnableBroadcast_multiUser() {
-        int userId1 = 11;
-        int userId2 = 12;
-        setUsers(new int[]{userId1, userId2});
-        when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1});
-
-        TestableNotificationManagerService service = spy(mService);
-        setNASMigrationDone(false, userId1);
-        setNASMigrationDone(false, userId2);
-
-        simulateNASUpgradeBroadcast(ACTION_ENABLE_NAS, userId1);
-
-        assertTrue(service.isNASMigrationDone(userId1));
-        assertFalse(service.isNASMigrationDone(userId2));
-        verify(mAssistants, times(1)).resetDefaultFromConfig();
-
-        //TODO(b/192450820)
-        //service.migrateDefaultNASShowNotificationIfNecessary();
-        //verify(service, times(0)).createNASUpgradeNotification(eq(userId1));
-    }
-
-    @Test
-    public void testNASUpgradeNotificationLearnMoreBroadcast() {
-        int userId = 11;
-        setUsers(new int[]{userId});
-        TestableNotificationManagerService service = spy(mService);
-        setNASMigrationDone(false, userId);
-        doNothing().when(mContext).startActivity(any());
-
-        simulateNASUpgradeBroadcast(ACTION_LEARNMORE_NAS, userId);
-
-        verify(mContext, times(1)).startActivity(any(Intent.class));
-        assertFalse(service.isNASMigrationDone(userId));
-        //TODO(b/192450820)
-        //verify(service, times(0)).createNASUpgradeNotification(eq(userId));
-        verify(mAssistants, times(0)).resetDefaultFromConfig();
-    }
-
-
     private void setNASMigrationDone(boolean done, int userId) {
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.NAS_SETTINGS_UPDATED, done ? 1 : 0, userId);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
index a9fd3c9..0c8fe35 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java
@@ -27,10 +27,6 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.USER_SYSTEM;
 
-import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS;
-import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS;
-import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -252,7 +248,6 @@
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
-    BroadcastReceiver mNASIntentReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
@@ -407,14 +402,9 @@
                     && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED)
                     && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) {
                 mPackageIntentReceiver = broadcastReceivers.get(i);
-            } else if (filter.hasAction(ACTION_ENABLE_NAS)
-                    && filter.hasAction(ACTION_DISABLE_NAS)
-                    && filter.hasAction(ACTION_LEARNMORE_NAS)) {
-                mNASIntentReceiver = broadcastReceivers.get(i);
             }
         }
         assertNotNull("package intent receiver should exist", mPackageIntentReceiver);
-        assertNotNull("nas intent receiver should exist", mNASIntentReceiver);
 
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
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 c85e876..d49cf67 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -31,6 +31,7 @@
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
+import static android.os.UserHandle.USER_SYSTEM;
 import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
 
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -545,14 +546,14 @@
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_NONE);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
-                UserHandle.USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
+                USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
         mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_N_MR1, PKG_O},
                 new int[]{UID_N_MR1, UID_O});
 
         mHelper.setShowBadge(PKG_O, UID_O, true);
 
-        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
+        loadStreamXml(baos, true, USER_SYSTEM);
 
         assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG_O, UID_O));
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
@@ -597,17 +598,22 @@
                 mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
 
         String xml = "<ranking version=\"2\">\n"
-                + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+                + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
+                + "\" show_badge=\"true\">\n"
                 + "<channel id=\"idn\" name=\"name\" importance=\"2\" />\n"
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
                 + "</package>\n"
-                + "<package name=\"" + PKG_O + "\" importance=\"0\">\n"
+                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" importance=\"0\">\n"
                 + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
                 + "</package>\n"
-                + "<package name=\"" + PKG_P + "\" importance=\"2\">\n"
+                + "<package name=\"" + PKG_P + "\" uid=\"" + UID_P + "\" importance=\"2\">\n"
                 + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
                 + "</package>\n"
                 + "</ranking>\n";
+
+        loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM);
+
+        // expected values
         NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW);
         idn.setSound(null, new AudioAttributes.Builder()
                 .setUsage(USAGE_NOTIFICATION)
@@ -637,8 +643,7 @@
         // Notifications enabled, user set b/c channel modified
         PackagePermission pExpected = new PackagePermission(PKG_P, 0, true, true);
 
-        loadByteArrayXml(xml.getBytes(), true, UserHandle.USER_SYSTEM);
-
+        // verify data
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
 
         assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false));
@@ -651,6 +656,85 @@
     }
 
     @Test
+    public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception {
+        when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
+        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+                mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory);
+
+        when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getPackageUidAsUser("pkg3", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
+        when(mPm.getApplicationInfoAsUser(eq("pkg1"), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+        when(mPm.getApplicationInfoAsUser(eq("pkg2"), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+        when(mPm.getApplicationInfoAsUser(eq("pkg3"), anyInt(), anyInt())).thenThrow(
+                new PackageManager.NameNotFoundException());
+
+        String xml = "<ranking version=\"2\">\n"
+                + "<package name=\"pkg1\" show_badge=\"true\">\n"
+                + "<channel id=\"idn\" name=\"name\" importance=\"2\" />\n"
+                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n"
+                + "</package>\n"
+                + "<package name=\"pkg2\" importance=\"0\">\n"
+                + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n"
+                + "</package>\n"
+                + "<package name=\"pkg3\" importance=\"2\">\n"
+                + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
+        NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW);
+        idn.setSound(null, new AudioAttributes.Builder()
+                .setUsage(USAGE_NOTIFICATION)
+                .setContentType(CONTENT_TYPE_SONIFICATION)
+                .setFlags(0)
+                .build());
+        idn.setShowBadge(false);
+        NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW);
+        ido.setShowBadge(true);
+        ido.setSound(null, new AudioAttributes.Builder()
+                .setUsage(USAGE_NOTIFICATION)
+                .setContentType(CONTENT_TYPE_SONIFICATION)
+                .setFlags(0)
+                .build());
+        NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH);
+        idp.lockFields(2);
+        idp.setSound(null, new AudioAttributes.Builder()
+                .setUsage(USAGE_NOTIFICATION)
+                .setContentType(CONTENT_TYPE_SONIFICATION)
+                .setFlags(0)
+                .build());
+
+        // Notifications enabled, not user set
+        PackagePermission pkg1Expected = new PackagePermission("pkg1", 0, true, false);
+        // Notifications not enabled, so user set
+        PackagePermission pkg2Expected = new PackagePermission("pkg2", 0, false, true);
+        // Notifications enabled, user set b/c channel modified
+        PackagePermission pkg3Expected = new PackagePermission("pkg3", 0, true, true);
+
+        loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
+
+        verify(mPermissionHelper, never()).setNotificationPermission(any());
+
+        when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(11);
+        when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(12);
+        when(mPm.getPackageUidAsUser("pkg3", USER_SYSTEM)).thenReturn(13);
+
+        mHelper.onPackagesChanged(
+                false, 0, new String[]{"pkg1", "pkg2", "pkg3"}, new int[] {11, 12, 13});
+
+        assertTrue(mHelper.canShowBadge("pkg1", 11));
+
+        assertEquals(idn, mHelper.getNotificationChannel("pkg1", 11, idn.getId(), false));
+        compareChannels(ido, mHelper.getNotificationChannel("pkg2", 12, ido.getId(), false));
+        compareChannels(idp, mHelper.getNotificationChannel("pkg3", 13, idp.getId(), false));
+
+        verify(mPermissionHelper).setNotificationPermission(pkg1Expected);
+        verify(mPermissionHelper).setNotificationPermission(pkg2Expected);
+        verify(mPermissionHelper).setNotificationPermission(pkg3Expected);
+    }
+
+    @Test
     public void testReadXml_newXml_noMigration() throws Exception {
         when(mPermissionHelper.isMigrationEnabled()).thenReturn(true);
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
@@ -690,7 +774,7 @@
                 .setFlags(0)
                 .build());
 
-        loadByteArrayXml(xml.getBytes(), true, UserHandle.USER_SYSTEM);
+        loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM);
 
         assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
 
@@ -714,7 +798,7 @@
         appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
         appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
 
-        when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
+        when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
@@ -748,7 +832,7 @@
         mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
-                PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_SYSTEM);
+                PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM);
         String expected = "<ranking version=\"3\">\n"
                 + "<package name=\"com.example.o\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -795,7 +879,7 @@
         appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
         appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
 
-        when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
+        when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
@@ -829,7 +913,7 @@
         mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
-                PKG_N_MR1, UID_N_MR1, true, UserHandle.USER_SYSTEM);
+                PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
         String expected = "<ranking version=\"3\">\n"
                 // Importance 0 because off in permissionhelper
                 + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
@@ -878,7 +962,7 @@
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair(UID_P, PKG_P), new Pair(true, false));
         appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
-        when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
+        when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
         NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
@@ -912,7 +996,7 @@
         mHelper.setInvalidMsgAppDemoted(PKG_P, UID_P, true);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
-                PKG_N_MR1, UID_N_MR1, true, UserHandle.USER_SYSTEM);
+                PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
         String expected = "<ranking version=\"3\">\n"
                 // Importance 0 because off in permissionhelper
                 + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" "
@@ -962,11 +1046,11 @@
         appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false));
         appPermissions.put(new Pair(UID_N_MR1, PKG_N_MR1), new Pair(true, false));
 
-        when(mPermissionHelper.getNotificationPermissionValues(UserHandle.USER_SYSTEM))
+        when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
                 .thenReturn(appPermissions);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(
-                PKG_N_MR1, UID_N_MR1, true, UserHandle.USER_SYSTEM);
+                PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM);
         String expected = "<ranking version=\"3\">\n"
                 // Packages that exist solely in permissionhelper
                 + "<package name=\"" + PKG_P + "\" importance=\"3\" />\n"
@@ -986,10 +1070,10 @@
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
-                UserHandle.USER_SYSTEM, channel.getId());
+                USER_SYSTEM, channel.getId());
 
         // Testing that in restore we are given the canonical version
-        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
+        loadStreamXml(baos, true, USER_SYSTEM);
         verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
     }
 
@@ -1012,9 +1096,9 @@
         channel.setSound(SOUND_URI, mAudioAttributes);
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
-                UserHandle.USER_SYSTEM, channel.getId());
+                USER_SYSTEM, channel.getId());
 
-        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
+        loadStreamXml(baos, true, USER_SYSTEM);
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
@@ -1034,9 +1118,9 @@
         channel.setSound(SOUND_URI, mAudioAttributes);
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
-                UserHandle.USER_SYSTEM, channel.getId());
+                USER_SYSTEM, channel.getId());
 
-        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
+        loadStreamXml(baos, true, USER_SYSTEM);
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
@@ -1065,7 +1149,7 @@
                 + "</ranking>\n";
 
         loadByteArrayXml(
-                backupWithUncanonicalizedSoundUri.getBytes(), true, UserHandle.USER_SYSTEM);
+                backupWithUncanonicalizedSoundUri.getBytes(), true, USER_SYSTEM);
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
         assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
@@ -1078,9 +1162,9 @@
         channel.setSound(null, mAudioAttributes);
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false);
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
-                UserHandle.USER_SYSTEM, channel.getId());
+                USER_SYSTEM, channel.getId());
 
-        loadStreamXml(baos, true, UserHandle.USER_SYSTEM);
+        loadStreamXml(baos, true, USER_SYSTEM);
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
@@ -1111,7 +1195,7 @@
                 mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, true,
-                UserHandle.USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
+                USER_SYSTEM, channel1.getId(), channel2.getId(), channel3.getId(),
                 NotificationChannel.DEFAULT_CHANNEL_ID);
         mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_N_MR1}, new int[]{
                 UID_N_MR1});
@@ -1120,7 +1204,7 @@
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
                 null);
         parser.nextTag();
-        mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+        mHelper.readXml(parser, true, USER_SYSTEM);
 
         assertNull(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
         assertNull(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId(), false));
@@ -2218,7 +2302,7 @@
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
 
-        assertTrue(mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1},
+        assertTrue(mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1},
                 new int[]{UID_N_MR1}));
 
         assertEquals(0, mHelper.getNotificationChannels(
@@ -2227,7 +2311,7 @@
         // Not deleted
         mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false);
 
-        assertFalse(mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM,
+        assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM,
                 new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
         assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
     }
@@ -2236,7 +2320,7 @@
     public void testOnPackageChanged_packageRemoval_importance() throws Exception {
         mHelper.setImportance(PKG_N_MR1, UID_N_MR1, NotificationManager.IMPORTANCE_HIGH);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
+        mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
                 UID_N_MR1});
 
         assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG_N_MR1,
@@ -2250,7 +2334,7 @@
         NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
         mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
 
-        mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
+        mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
                 UID_N_MR1});
 
         assertEquals(0, mHelper.getNotificationChannelGroups(
@@ -2267,7 +2351,7 @@
         legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
         when(mPm.getApplicationInfoAsUser(eq(PKG_O), anyInt(), anyInt())).thenReturn(legacy);
         mHelper.onPackagesChanged(
-                false, UserHandle.USER_SYSTEM, new String[]{PKG_O}, new int[]{UID_O});
+                false, USER_SYSTEM, new String[]{PKG_O}, new int[]{UID_O});
 
         // make sure the default channel was readded
         //assertEquals(2, mHelper.getNotificationChannels(PKG_O, UID_O, false).getList().size());
@@ -3224,7 +3308,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory);
-        loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM);
+        loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM);
 
         assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS,
                 mHelper.shouldHideSilentStatusIcons());
@@ -4058,7 +4142,7 @@
 
         // clear data
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, true,
-                UserHandle.USER_SYSTEM, channel1.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+                USER_SYSTEM, channel1.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
         mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG_O}, new int[]{
                 UID_O});
 
@@ -4070,7 +4154,7 @@
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
                 null);
         parser.nextTag();
-        mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+        mHelper.readXml(parser, true, USER_SYSTEM);
 
         assertTrue(mHelper.getNotificationChannel(PKG_O, UID_O, channel1.getId(), false)
                 .isImportanceLockedByCriticalDeviceFunction());
@@ -4357,7 +4441,7 @@
         assertTrue(nc1.isDeleted());
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_P, UID_P, false,
-                UserHandle.USER_SYSTEM, "id", NotificationChannel.DEFAULT_CHANNEL_ID);
+                USER_SYSTEM, "id", NotificationChannel.DEFAULT_CHANNEL_ID);
 
         TypedXmlPullParser parser = Xml.newFastPullParser();
         parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
@@ -4366,7 +4450,7 @@
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
                 mPermissionHelper, mLogger,
                 mAppOpsManager, mStatsEventBuilderFactory);
-        mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);
+        mHelper.readXml(parser, true, USER_SYSTEM);
 
         NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
         assertTrue(DateUtils.isToday(nc.getDeletedTimeMs()));
@@ -5028,6 +5112,11 @@
                 assertTrue(expected.containsKey(uid));
                 assertThat(expected.get(uid)).isEqualTo(
                             builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
+
+                // pre-migration, the userSet field will always default to false
+                boolean userSet = builder.getBoolean(
+                        PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
+                assertFalse(userSet);
             }
         }
     }
@@ -5039,8 +5128,8 @@
         // build a collection of app permissions that should be passed in but ignored
         ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
         appPermissions.put(new Pair(1, "first"), new Pair(true, false));    // not in local prefs
-        appPermissions.put(new Pair(3, "third"), new Pair(false, false));   // not in local prefs
-        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, false)); // in local prefs
+        appPermissions.put(new Pair(3, "third"), new Pair(false, true));   // not in local prefs
+        appPermissions.put(new Pair(UID_O, PKG_O), new Pair(false, true)); // in local prefs
 
         // package preferences: PKG_O not banned based on local importance, and PKG_P is
         mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_HIGH);
@@ -5048,11 +5137,11 @@
 
         // expected output. format: uid -> importance, as only uid (and not package name)
         // is in PackageNotificationPreferences
-        ArrayMap<Integer, Integer> expected = new ArrayMap<>();
-        expected.put(1, IMPORTANCE_DEFAULT);
-        expected.put(3, IMPORTANCE_NONE);
-        expected.put(UID_O, IMPORTANCE_NONE);    // banned by permissions
-        expected.put(UID_P, IMPORTANCE_NONE);    // defaults to none
+        ArrayMap<Integer, Pair<Integer, Boolean>> expected = new ArrayMap<>();
+        expected.put(1, new Pair(IMPORTANCE_DEFAULT, false));
+        expected.put(3, new Pair(IMPORTANCE_NONE, true));
+        expected.put(UID_O, new Pair(IMPORTANCE_NONE, true));     // banned by permissions
+        expected.put(UID_P, new Pair(IMPORTANCE_NONE, false));    // defaults to none, false
 
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackagePreferencesStats(events, appPermissions);
@@ -5060,11 +5149,14 @@
         for (WrappedSysUiStatsEvent.WrappedBuilder builder : mStatsEventBuilderFactory.builders) {
             if (builder.getAtomId() == PACKAGE_NOTIFICATION_PREFERENCES) {
                 int uid = builder.getInt(PackageNotificationPreferences.UID_FIELD_NUMBER);
+                boolean userSet = builder.getBoolean(
+                        PackageNotificationPreferences.USER_SET_IMPORTANCE_FIELD_NUMBER);
 
                 // if it's one of the expected ids, then make sure the importance matches
                 assertTrue(expected.containsKey(uid));
-                assertThat(expected.get(uid)).isEqualTo(
+                assertThat(expected.get(uid).first).isEqualTo(
                         builder.getInt(PackageNotificationPreferences.IMPORTANCE_FIELD_NUMBER));
+                assertThat(expected.get(uid).second).isEqualTo(userSet);
             }
         }
     }
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 3e617d5..55d6df9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -34,6 +34,7 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.Context;
@@ -58,6 +59,7 @@
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.pm.PackageManagerService;
+import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult;
 
 import org.junit.After;
 import org.junit.Before;
@@ -298,35 +300,44 @@
         assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
     }
 
-    public void addMockInterceptorCallback(@Nullable Intent intent) {
+    public void addMockInterceptorCallback(
+            @Nullable Intent intent, @Nullable ActivityOptions activityOptions) {
         int size = mActivityInterceptorCallbacks.size();
         mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() {
             @Override
-            public Intent intercept(ActivityInterceptorInfo info) {
-                return intent;
+            public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                if (intent == null && activityOptions == null) {
+                    return null;
+                }
+                return new ActivityInterceptResult(
+                        intent != null ? intent : info.intent,
+                        activityOptions != null ? activityOptions : info.checkedOptions);
             }
         });
     }
 
     @Test
     public void testInterceptionCallback_singleCallback() {
-        addMockInterceptorCallback(new Intent("android.test.foo"));
+        addMockInterceptorCallback(
+                new Intent("android.test.foo"),
+                ActivityOptions.makeBasic().setLaunchDisplayId(3));
 
         assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
         assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
+        assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
     }
 
     @Test
     public void testInterceptionCallback_singleCallbackReturnsNull() {
-        addMockInterceptorCallback(null);
+        addMockInterceptorCallback(null, null);
 
         assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
     }
 
     @Test
     public void testInterceptionCallback_fallbackToSecondCallback() {
-        addMockInterceptorCallback(null);
-        addMockInterceptorCallback(new Intent("android.test.second"));
+        addMockInterceptorCallback(null, null);
+        addMockInterceptorCallback(new Intent("android.test.second"), null);
 
         assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
         assertEquals("android.test.second", mInterceptor.mIntent.getAction());
@@ -334,7 +345,7 @@
 
     @Test
     public void testActivityLaunchedCallback_singleCallback() {
-        addMockInterceptorCallback(null);
+        addMockInterceptorCallback(null, null);
 
         assertEquals(1, mActivityInterceptorCallbacks.size());
         final ActivityInterceptorCallback callback = mActivityInterceptorCallbacks.valueAt(0);
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 e0072b4..a2b04c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -53,7 +53,6 @@
 import android.app.PictureInPictureParams;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.EnterPipRequestedItem;
-import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
@@ -951,7 +950,7 @@
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public Intent intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -963,7 +962,7 @@
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public Intent intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -975,7 +974,7 @@
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public Intent intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -983,7 +982,7 @@
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public Intent intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -997,7 +996,7 @@
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public Intent intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
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 c0959d3..2ea7fda 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -22,6 +22,9 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+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_CRASHING_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -92,6 +95,7 @@
         final ActivityRecord activity = createActivityRecord(dc);
 
         mDc.prepareAppTransition(TRANSIT_OPEN);
+        mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
         mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
         mDc.mOpeningApps.add(activity);
         assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
@@ -102,6 +106,22 @@
     }
 
     @Test
+    public void testKeyguardUnoccludeOcclude() {
+        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+        final ActivityRecord activity = createActivityRecord(dc);
+
+        mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
+        mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
+        mDc.mOpeningApps.add(activity);
+        assertEquals(TRANSIT_NONE,
+                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
+                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+
+    }
+
+    @Test
     public void testKeyguardKeep() {
         final DisplayContent dc = createNewDisplay(Display.STATE_ON);
         final ActivityRecord activity = createActivityRecord(dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index f5b361c..97b1c91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
 import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
 import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
@@ -56,7 +55,6 @@
 
     static final int DISPLAY_WIDTH = 500;
     static final int DISPLAY_HEIGHT = 1000;
-    static final int DISPLAY_DENSITY = 320;
 
     static final int DISPLAY_CUTOUT_HEIGHT = 8;
     static final int IME_HEIGHT = 415;
@@ -85,12 +83,7 @@
         doReturn(true).when(mDisplayPolicy).hasNavigationBar();
         doReturn(true).when(mDisplayPolicy).hasStatusBar();
 
-        final int shortSizeDp =
-                Math.min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * DENSITY_DEFAULT / DISPLAY_DENSITY;
-        final int longSizeDp =
-                Math.min(DISPLAY_WIDTH, DISPLAY_HEIGHT) * DENSITY_DEFAULT / DISPLAY_DENSITY;
-        mDisplayContent.getDisplayRotation().configure(
-                DISPLAY_WIDTH, DISPLAY_HEIGHT, shortSizeDp, longSizeDp);
+        mDisplayContent.getDisplayRotation().configure(DISPLAY_WIDTH, DISPLAY_HEIGHT);
         mDisplayPolicy.onConfigurationChanged();
 
         addWindow(mStatusBarWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index e1aca55..6342183 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -681,7 +681,7 @@
     }
 
     /**
-     * Call {@link DisplayRotation#configure(int, int, int, int)} to configure {@link #mTarget}
+     * Call {@link DisplayRotation#configure(int, int)} to configure {@link #mTarget}
      * according to given parameters.
      */
     private void configureDisplayRotation(int displayOrientation, boolean isCar, boolean isTv) {
@@ -709,9 +709,7 @@
         when(mockPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
                 .thenReturn(isTv);
 
-        final int shortSizeDp = (isCar || isTv) ? 540 : 720;
-        final int longSizeDp = 960;
-        mTarget.configure(width, height, shortSizeDp, longSizeDp);
+        mTarget.configure(width, height);
     }
 
     private void freezeRotation(int rotation) {
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 c4cccf0..1924760 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -104,7 +104,8 @@
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
         assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertEquals(LOW_REFRESH_RATE,
+                mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         mPolicy.removeNonHighRefreshRatePackage("com.android.test");
@@ -165,7 +166,8 @@
         assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
         assertEquals(HI_REFRESH_RATE,
                 mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertEquals(LOW_REFRESH_RATE,
+                mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -180,7 +182,8 @@
         assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
         assertEquals(HI_REFRESH_RATE,
                 mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+        assertEquals(LOW_REFRESH_RATE,
+                mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
     }
@@ -257,7 +260,8 @@
         mPolicy.addNonHighRefreshRatePackage("com.android.test");
         assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
         assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
-        assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+        assertEquals(LOW_REFRESH_RATE,
+                mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
         assertEquals(LOW_REFRESH_RATE,
                 mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
 
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 b7417c4..ec6cd92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -488,7 +488,8 @@
         final TestTransitionPlayer player = registerTestTransitionPlayer();
 
         mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
-        mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */);
+        mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
+                null /* displayChange */);
         final FadeRotationAnimationController fadeController =
                 mDisplayContent.getFadeRotationAnimationController();
         assertNotNull(fadeController);
@@ -524,6 +525,43 @@
     }
 
     @Test
+    public void testAppTransitionWithRotationChange() {
+        final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar");
+        makeWindowVisible(statusBar);
+        mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs);
+        final ActivityRecord app = createActivityRecord(mDisplayContent);
+        final TestTransitionPlayer player = registerTestTransitionPlayer();
+        final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN);
+        app.mTransitionController.requestStartTransition(transition, app.getTask(),
+                null /* remoteTransition */, null /* displayChange */);
+        mDisplayContent.getDisplayRotation().setRotation(mDisplayContent.getRotation() + 1);
+        final int anyChanges = 1;
+        mDisplayContent.requestChangeTransitionIfNeeded(anyChanges, null /* displayChange */);
+        transition.setKnownConfigChanges(mDisplayContent, anyChanges);
+        final FadeRotationAnimationController fadeController =
+                mDisplayContent.getFadeRotationAnimationController();
+        assertNotNull(fadeController);
+        assertTrue(fadeController.shouldFreezeInsetsPosition(statusBar));
+
+        statusBar.setOrientationChanging(true);
+        player.startTransition();
+        // Non-app windows should not be collected.
+        assertFalse(statusBar.mToken.inTransition());
+        assertTrue(app.getTask().inTransition());
+
+        final SurfaceControl.Transaction startTransaction = mock(SurfaceControl.Transaction.class);
+        player.onTransactionReady(startTransaction);
+        // The leash should be unrotated.
+        verify(startTransaction).setMatrix(eq(statusBar.mToken.getAnimationLeash()), any(), any());
+
+        // The redrawn window will be faded in when the transition finishes. And because this test
+        // only use one non-activity window, the fade rotation controller should also be cleared.
+        statusBar.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
+        player.finish();
+        assertNull(mDisplayContent.getFadeRotationAnimationController());
+    }
+
+    @Test
     public void testIntermediateVisibility() {
         final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
         final TransitionController controller = new TransitionController(mAtm, snapshotController);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 2d3b928..88725a6 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -552,23 +552,26 @@
         private static final int MSG_PACKAGE_REMOVED = 103;
         /**
          * By only triggering a re-calculation after the storage has changed sizes, we can avoid
-         * recalculating quotas too often. Minimum change delta defines the percentage of change
-         * we need to see before we recalculate.
+         * recalculating quotas too often. Minimum change delta high and low define the
+         * percentage of change we need to see before we recalculate quotas when the device has
+         * enough storage space (more than StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH of total
+         * free) and in low storage condition respectively.
          */
-        private static final double MINIMUM_CHANGE_DELTA = 0.05;
+        private static final long MINIMUM_CHANGE_DELTA_PERCENT_HIGH = 5;
+        private static final long MINIMUM_CHANGE_DELTA_PERCENT_LOW = 2;
         private static final int UNSET = -1;
         private static final boolean DEBUG = false;
 
         private final StatFs mStats;
         private long mPreviousBytes;
-        private double mMinimumThresholdBytes;
+        private long mTotalBytes;
 
         public H(Looper looper) {
             super(looper);
             // TODO: Handle all private volumes.
             mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
             mPreviousBytes = mStats.getAvailableBytes();
-            mMinimumThresholdBytes = mStats.getTotalBytes() * MINIMUM_CHANGE_DELTA;
+            mTotalBytes = mStats.getTotalBytes();
         }
 
         public void handleMessage(Message msg) {
@@ -584,7 +587,14 @@
                 case MSG_CHECK_STORAGE_DELTA: {
                     mStats.restat(Environment.getDataDirectory().getAbsolutePath());
                     long bytesDelta = Math.abs(mPreviousBytes - mStats.getAvailableBytes());
-                    if (bytesDelta > mMinimumThresholdBytes) {
+                    long bytesDeltaThreshold;
+                    if (mStats.getAvailableBytes() >  mTotalBytes
+                            * StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH / 100) {
+                        bytesDeltaThreshold = mTotalBytes * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100;
+                    } else {
+                        bytesDeltaThreshold = mTotalBytes * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100;
+                    }
+                    if (bytesDelta > bytesDeltaThreshold) {
                         mPreviousBytes = mStats.getAvailableBytes();
                         recalculateQuotas(getInitializedStrategy());
                         notifySignificantDelta();
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index 4b90985..fccdf76 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -248,10 +248,10 @@
             int route = source.readInt();
             int supportedRouteMask = source.readInt();
             BluetoothDevice activeBluetoothDevice = source.readParcelable(
-                    ClassLoader.getSystemClassLoader());
+                    ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class);
             List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
             source.readParcelableList(supportedBluetoothDevices,
-                    ClassLoader.getSystemClassLoader());
+                    ClassLoader.getSystemClassLoader(), android.bluetooth.BluetoothDevice.class);
             return new CallAudioState(isMuted, route,
                     supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
         }
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 9fec96b..21a1804 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -837,6 +837,17 @@
             "android.telecom.extra.AUDIO_CODEC_BANDWIDTH_KHZ";
 
     /**
+     * Last known cell identity key to be used to fill geo location header in case of an emergency
+     * call. This entry will not be filled if call is not identified as an emergency call.
+     * {@link Connection}. Only provided to the {@link ConnectionService} for the purpose
+     * of placing an emergency call; will not be present in the {@link InCallService} layer.
+     * The {@link ConnectionService}'s implementation will be logged for fine location access
+     * when an outgoing call is placed in this case.
+     */
+    public static final String EXTRA_LAST_KNOWN_CELL_IDENTITY =
+            "android.telecom.extra.LAST_KNOWN_CELL_IDENTITY";
+
+    /**
      * Boolean connection extra key used to indicate whether device to device communication is
      * available for the current call.
      * @hide
@@ -3526,9 +3537,9 @@
             mIsBlocked = in.readByte() != 0;
             mIsInContacts = in.readByte() != 0;
             CallScreeningService.ParcelableCallResponse response
-                    = in.readParcelable(CallScreeningService.class.getClassLoader());
+                    = in.readParcelable(CallScreeningService.class.getClassLoader(), android.telecom.CallScreeningService.ParcelableCallResponse.class);
             mCallResponse = response == null ? null : response.toCallResponse();
-            mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader());
+            mCallScreeningComponent = in.readParcelable(ComponentName.class.getClassLoader(), android.content.ComponentName.class);
         }
 
         @NonNull
diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java
index be5fae4..1172e13 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -272,17 +272,17 @@
     }
 
     private ConnectionRequest(Parcel in) {
-        mAccountHandle = in.readParcelable(getClass().getClassLoader());
-        mAddress = in.readParcelable(getClass().getClassLoader());
-        mExtras = in.readParcelable(getClass().getClassLoader());
+        mAccountHandle = in.readParcelable(getClass().getClassLoader(), android.telecom.PhoneAccountHandle.class);
+        mAddress = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
+        mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class);
         mVideoState = in.readInt();
         mTelecomCallId = in.readString();
         mShouldShowIncomingCallUi = in.readInt() == 1;
-        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader());
-        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader());
+        mRttPipeFromInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mRttPipeToInCall = in.readParcelable(getClass().getClassLoader(), android.os.ParcelFileDescriptor.class);
 
         mParticipants = new ArrayList<Uri>();
-        in.readList(mParticipants, getClass().getClassLoader());
+        in.readList(mParticipants, getClass().getClassLoader(), android.net.Uri.class);
 
         mIsAdhocConference = in.readInt() == 1;
     }
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index ed7b79f..0f034ad 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -287,7 +287,7 @@
             int tone = source.readInt();
             int telephonyDisconnectCause = source.readInt();
             int telephonyPreciseDisconnectCause = source.readInt();
-            ImsReasonInfo imsReasonInfo = source.readParcelable(null);
+            ImsReasonInfo imsReasonInfo = source.readParcelable(null, android.telephony.ims.ImsReasonInfo.class);
             return new DisconnectCause(code, label, description, reason, tone,
                     telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
         }
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 320308c..f412a18 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -623,9 +623,9 @@
             ClassLoader classLoader = ParcelableCall.class.getClassLoader();
             String id = source.readString();
             int state = source.readInt();
-            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
             List<String> cannedSmsResponses = new ArrayList<>();
-            source.readList(cannedSmsResponses, classLoader);
+            source.readList(cannedSmsResponses, classLoader, java.lang.String.class);
             int capabilities = source.readInt();
             int properties = source.readInt();
             long connectTimeMillis = source.readLong();
@@ -633,23 +633,23 @@
             int handlePresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
-            GatewayInfo gatewayInfo = source.readParcelable(classLoader);
-            PhoneAccountHandle accountHandle = source.readParcelable(classLoader);
+            GatewayInfo gatewayInfo = source.readParcelable(classLoader, android.telecom.GatewayInfo.class);
+            PhoneAccountHandle accountHandle = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
             boolean isVideoCallProviderChanged = source.readByte() == 1;
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             String parentCallId = source.readString();
             List<String> childCallIds = new ArrayList<>();
-            source.readList(childCallIds, classLoader);
-            StatusHints statusHints = source.readParcelable(classLoader);
+            source.readList(childCallIds, classLoader, java.lang.String.class);
+            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
             int videoState = source.readInt();
             List<String> conferenceableCallIds = new ArrayList<>();
-            source.readList(conferenceableCallIds, classLoader);
+            source.readList(conferenceableCallIds, classLoader, java.lang.String.class);
             Bundle intentExtras = source.readBundle(classLoader);
             Bundle extras = source.readBundle(classLoader);
             int supportedAudioRoutes = source.readInt();
             boolean isRttCallChanged = source.readByte() == 1;
-            ParcelableRttCall rttCall = source.readParcelable(classLoader);
+            ParcelableRttCall rttCall = source.readParcelable(classLoader, android.telecom.ParcelableRttCall.class);
             long creationTimeMillis = source.readLong();
             int callDirection = source.readInt();
             int callerNumberVerificationStatus = source.readInt();
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index 1f8aafb..e57c833 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -292,24 +292,24 @@
         @Override
         public ParcelableConference createFromParcel(Parcel source) {
             ClassLoader classLoader = ParcelableConference.class.getClassLoader();
-            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
             int state = source.readInt();
             int capabilities = source.readInt();
             List<String> connectionIds = new ArrayList<>(2);
-            source.readList(connectionIds, classLoader);
+            source.readList(connectionIds, classLoader, java.lang.String.class);
             long connectTimeMillis = source.readLong();
             IVideoProvider videoCallProvider =
                     IVideoProvider.Stub.asInterface(source.readStrongBinder());
             int videoState = source.readInt();
-            StatusHints statusHints = source.readParcelable(classLoader);
+            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
             Bundle extras = source.readBundle(classLoader);
             int properties = source.readInt();
             long connectElapsedTimeMillis = source.readLong();
-            Uri address = source.readParcelable(classLoader);
+            Uri address = source.readParcelable(classLoader, android.net.Uri.class);
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
-            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
             boolean isRingbackRequested = source.readInt() == 1;
             int callDirection = source.readInt();
 
diff --git a/telecomm/java/android/telecom/ParcelableConnection.java b/telecomm/java/android/telecom/ParcelableConnection.java
index 2b9ce9b..7b83338 100644
--- a/telecomm/java/android/telecom/ParcelableConnection.java
+++ b/telecomm/java/android/telecom/ParcelableConnection.java
@@ -261,10 +261,10 @@
         public ParcelableConnection createFromParcel(Parcel source) {
             ClassLoader classLoader = ParcelableConnection.class.getClassLoader();
 
-            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader);
+            PhoneAccountHandle phoneAccount = source.readParcelable(classLoader, android.telecom.PhoneAccountHandle.class);
             int state = source.readInt();
             int capabilities = source.readInt();
-            Uri address = source.readParcelable(classLoader);
+            Uri address = source.readParcelable(classLoader, android.net.Uri.class);
             int addressPresentation = source.readInt();
             String callerDisplayName = source.readString();
             int callerDisplayNamePresentation = source.readInt();
@@ -274,8 +274,8 @@
             boolean ringbackRequested = source.readByte() == 1;
             boolean audioModeIsVoip = source.readByte() == 1;
             long connectTimeMillis = source.readLong();
-            StatusHints statusHints = source.readParcelable(classLoader);
-            DisconnectCause disconnectCause = source.readParcelable(classLoader);
+            StatusHints statusHints = source.readParcelable(classLoader, android.telecom.StatusHints.class);
+            DisconnectCause disconnectCause = source.readParcelable(classLoader, android.telecom.DisconnectCause.class);
             List<String> conferenceableConnectionIds = new ArrayList<>();
             source.readStringList(conferenceableConnectionIds);
             Bundle extras = Bundle.setDefusable(source.readBundle(classLoader), true);
diff --git a/telecomm/java/android/telecom/ParcelableRttCall.java b/telecomm/java/android/telecom/ParcelableRttCall.java
index fbcf486..b88473a 100644
--- a/telecomm/java/android/telecom/ParcelableRttCall.java
+++ b/telecomm/java/android/telecom/ParcelableRttCall.java
@@ -46,8 +46,8 @@
 
     protected ParcelableRttCall(Parcel in) {
         mRttMode = in.readInt();
-        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
-        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
+        mTransmitStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
+        mReceiveStream = in.readParcelable(ParcelFileDescriptor.class.getClassLoader(), android.os.ParcelFileDescriptor.class);
     }
 
     public static final @android.annotation.NonNull Creator<ParcelableRttCall> CREATOR = new Creator<ParcelableRttCall>() {
diff --git a/telecomm/java/android/telecom/PhoneAccountSuggestion.java b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
index 2589d95..d9f89d5 100644
--- a/telecomm/java/android/telecom/PhoneAccountSuggestion.java
+++ b/telecomm/java/android/telecom/PhoneAccountSuggestion.java
@@ -84,7 +84,7 @@
     }
 
     private PhoneAccountSuggestion(Parcel in) {
-        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader());
+        mHandle = in.readParcelable(PhoneAccountHandle.class.getClassLoader(), android.telecom.PhoneAccountHandle.class);
         mReason = in.readInt();
         mShouldAutoSelect = in.readByte() != 0;
     }
diff --git a/telecomm/java/android/telecom/StatusHints.java b/telecomm/java/android/telecom/StatusHints.java
index 762c93a..2faecc2 100644
--- a/telecomm/java/android/telecom/StatusHints.java
+++ b/telecomm/java/android/telecom/StatusHints.java
@@ -132,8 +132,8 @@
 
     private StatusHints(Parcel in) {
         mLabel = in.readCharSequence();
-        mIcon = in.readParcelable(getClass().getClassLoader());
-        mExtras = in.readParcelable(getClass().getClassLoader());
+        mIcon = in.readParcelable(getClass().getClassLoader(), android.graphics.drawable.Icon.class);
+        mExtras = in.readParcelable(getClass().getClassLoader(), android.os.Bundle.class);
     }
 
     @Override
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 10fba60..1ba997f 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -107,6 +107,26 @@
         }
     }
 
+
+    /**
+     * Check whether the caller (or self, if not processing an IPC) has non dangerous
+     * read phone state permission.
+     * @param context app context
+     * @param message detail message
+     * @return true if permission is granted, else false
+     */
+    public static boolean checkCallingOrSelfReadNonDangerousPhoneStateNoThrow(
+            Context context, String message) {
+        try {
+            context.enforcePermission(
+                    Manifest.permission.READ_BASIC_PHONE_STATE,
+                    Binder.getCallingPid(), Binder.getCallingUid(), message);
+            return true;
+        } catch (SecurityException se) {
+            return false;
+        }
+    }
+
     /**
      * Check whether the app with the given pid/uid can read phone state.
      *
@@ -556,6 +576,17 @@
     }
 
     /**
+     * Check if the caller (or self, if not processing an IPC) has ACCESS_LAST_KNOWN_CELL_ID
+     * permission
+     *
+     * @return true if caller has ACCESS_LAST_KNOWN_CELL_ID permission else false.
+     */
+    public static boolean checkLastKnownCellIdAccessPermission(Context context) {
+        return context.checkCallingOrSelfPermission("android.permission.ACCESS_LAST_KNOWN_CELL_ID")
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
      * Ensure the caller (or self, if not processing an IPC) has
      * {@link android.Manifest.permission#READ_PHONE_STATE} or carrier privileges.
      *
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 3c799e0..8c78f74 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -101,6 +101,25 @@
         }
     }
 
+    /**
+     * Convenience method for running the provided action in the provided
+     * executor enclosed in
+     * {@link Binder#clearCallingIdentity}/{@link Binder#restoreCallingIdentity}
+     *
+     * Any exception thrown by the given action will need to be handled by caller.
+     *
+     */
+    public static void runWithCleanCallingIdentity(
+            @NonNull Runnable action, @NonNull Executor executor) {
+        if (action != null) {
+            if (executor != null) {
+                executor.execute(() -> runWithCleanCallingIdentity(action));
+            } else {
+                runWithCleanCallingIdentity(action);
+            }
+        }
+    }
+
 
     /**
      * Convenience method for running the provided action enclosed in
diff --git a/telephony/java/android/service/carrier/CarrierService.java b/telephony/java/android/service/carrier/CarrierService.java
index d06ec11..ae91d4d 100644
--- a/telephony/java/android/service/carrier/CarrierService.java
+++ b/telephony/java/android/service/carrier/CarrierService.java
@@ -15,6 +15,8 @@
 package android.service.carrier;
 
 import android.annotation.CallSuper;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -22,9 +24,12 @@
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.ResultReceiver;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyRegistryManager;
 import android.util.Log;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
@@ -87,7 +92,54 @@
      * PersistableBundle} may be overridden by the system's default configuration service.
      * </p>
      *
-     * @param id contains details about the current carrier that can be used do decide what
+     * @param id contains details about the current carrier that can be used to decide what
+     *           configuration values to return. Instead of using details like MCCMNC to decide
+     *           current carrier, it also contains subscription carrier id
+     *           {@link android.telephony.TelephonyManager#getSimCarrierId()}, a platform-wide
+     *           unique identifier for each carrier, CarrierConfigService can directly use carrier
+     *           id as the key to look up the carrier info.
+     * @return a {@link PersistableBundle} object containing the configuration or null if default
+     *         values should be used.
+     * @deprecated use {@link #onLoadConfig(int, CarrierIdentifier)} instead.
+     */
+    @Deprecated
+    public abstract PersistableBundle onLoadConfig(CarrierIdentifier id);
+
+    /**
+     * Override this method to set carrier configuration on the given {@code subscriptionId}.
+     * <p>
+     * This method will be called by telephony services to get carrier-specific configuration
+     * values. The returned config will be saved by the system until,
+     * <ol>
+     * <li>The carrier app package is updated, or</li>
+     * <li>The carrier app requests a reload with
+     * {@link android.telephony.CarrierConfigManager#notifyConfigChangedForSubId
+     * notifyConfigChangedForSubId}.</li>
+     * </ol>
+     * This method can be called after a SIM card loads, which may be before or after boot.
+     * </p>
+     * <p>
+     * This method should not block for a long time. If expensive operations (e.g. network access)
+     * are required, this method can schedule the work and return null. Then, use
+     * {@link android.telephony.CarrierConfigManager#notifyConfigChangedForSubId
+     * notifyConfigChangedForSubId} to trigger a reload when the config is ready.
+     * </p>
+     * <p>
+     * Implementations should use the keys defined in {@link android.telephony.CarrierConfigManager
+     * CarrierConfigManager}. Any configuration values not set in the returned {@link
+     * PersistableBundle} may be overridden by the system's default configuration service.
+     * </p>
+     * <p>
+     * By default, this method just calls {@link #onLoadConfig(CarrierIdentifier)} with specified
+     * CarrierIdentifier {@code id}. Carrier app with target SDK
+     * {@link android.os.Build.VERSION_CODES#TIRAMISU} and above should override this method to
+     * load carrier configuration on the given {@code subscriptionId}.
+     * Note that {@link #onLoadConfig(CarrierIdentifier)} is still called prior to
+     * {@link android.os.Build.VERSION_CODES#TIRAMISU}.
+     * </p>
+     *
+     * @param subscriptionId the subscription on which the carrier app should load configuration
+     * @param id contains details about the current carrier that can be used to decide what
      *           configuration values to return. Instead of using details like MCCMNC to decide
      *           current carrier, it also contains subscription carrier id
      *           {@link android.telephony.TelephonyManager#getSimCarrierId()}, a platform-wide
@@ -96,7 +148,11 @@
      * @return a {@link PersistableBundle} object containing the configuration or null if default
      *         values should be used.
      */
-    public abstract PersistableBundle onLoadConfig(CarrierIdentifier id);
+    @SuppressLint("NullableCollection")
+    @Nullable
+    public PersistableBundle onLoadConfig(int subscriptionId, @Nullable CarrierIdentifier id) {
+        return onLoadConfig(id);
+    }
 
     /**
      * Informs the system of an intentional upcoming carrier network change by
@@ -115,7 +171,12 @@
      *               active. Set this value to true to begin showing
      *               alternative UI and false to stop.
      * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     * @deprecated use {@link #notifyCarrierNetworkChange(int, boolean)} instead.
+     *             With no parameter to specify the subscription, this API will
+     *             apply to all subscriptions that the carrier app has carrier
+     *             privileges on.
      */
+    @Deprecated
     public final void notifyCarrierNetworkChange(boolean active) {
         TelephonyRegistryManager telephonyRegistryMgr =
             (TelephonyRegistryManager) this.getSystemService(
@@ -126,6 +187,31 @@
     }
 
     /**
+     * Informs the system of an intentional upcoming carrier network change by a carrier app on the
+     * given {@code subscriptionId}. This call is optional and is only used to allow the system to
+     * provide alternative UI while telephony is performing an action that may result in
+     * intentional, temporary network lack of connectivity.
+     *
+     * <p>Based on the active parameter passed in, this method will either show or hide the
+     * alternative UI. There is no timeout associated with showing this UX, so a carrier app must
+     * be sure to call with active set to false sometime after calling with it set to true.
+     *
+     * <p>Requires Permission: calling app has carrier privileges.
+     *
+     * @param subscriptionId the subscription of the carrier network that trigger the change.
+     * @param active whether the carrier network change is or shortly will be active. Set this
+     *               value to true to begin showing alternative UI and false to stop.
+     * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+     */
+    public final void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
+        TelephonyRegistryManager telephonyRegistryMgr = this.getSystemService(
+                TelephonyRegistryManager.class);
+        if (telephonyRegistryMgr != null) {
+            telephonyRegistryMgr.notifyCarrierNetworkChange(subscriptionId, active);
+        }
+    }
+
+    /**
      * If overriding this method, call through to the super method for any unknown actions.
      * {@inheritDoc}
      */
@@ -149,10 +235,16 @@
         public static final String KEY_CONFIG_BUNDLE = "config_bundle";
 
         @Override
-        public void getCarrierConfig(CarrierIdentifier id, ResultReceiver result) {
+        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];
+                }
                 Bundle data = new Bundle();
-                data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(id));
+                data.putParcelable(KEY_CONFIG_BUNDLE, CarrierService.this.onLoadConfig(subId, id));
                 result.send(RESULT_OK, data);
             } catch (Exception e) {
                 Log.e(LOG_TAG, "Error in onLoadConfig: " + e.getMessage(), e);
diff --git a/telephony/java/android/service/carrier/ICarrierService.aidl b/telephony/java/android/service/carrier/ICarrierService.aidl
index ac6f9614..054a280 100644
--- a/telephony/java/android/service/carrier/ICarrierService.aidl
+++ b/telephony/java/android/service/carrier/ICarrierService.aidl
@@ -29,5 +29,5 @@
 interface ICarrierService {
 
     /** @see android.service.carrier.CarrierService#onLoadConfig */
-    oneway void getCarrierConfig(in CarrierIdentifier id, in ResultReceiver result);
+    oneway void getCarrierConfig(in int phoneId, in CarrierIdentifier id, in ResultReceiver result);
 }
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 1d7a476..4469ffc 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.hardware.radio.V1_5.AccessNetwork;
 
@@ -28,6 +29,8 @@
  */
 public final class AccessNetworkConstants {
 
+    private static final String TAG = AccessNetworkConstants.class.getSimpleName();
+
     /**
      * Wireless transportation type
      *
@@ -108,6 +111,21 @@
                 default: return Integer.toString(type);
             }
         }
+
+        /** @hide */
+        public static @RadioAccessNetworkType int fromString(@NonNull String str) {
+            switch (str.toUpperCase()) {
+                case "GERAN" : return GERAN;
+                case "UTRAN" : return UTRAN;
+                case "EUTRAN" : return EUTRAN;
+                case "CDMA2000" : return CDMA2000;
+                case "IWLAN" : return IWLAN;
+                case "NGRAN" : return NGRAN;
+                default:
+                    Rlog.e(TAG, "Invalid access network type " + str);
+                    return UNKNOWN;
+            }
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/AvailableNetworkInfo.java b/telephony/java/android/telephony/AvailableNetworkInfo.java
index 2b355ae..6d673fb 100644
--- a/telephony/java/android/telephony/AvailableNetworkInfo.java
+++ b/telephony/java/android/telephony/AvailableNetworkInfo.java
@@ -185,9 +185,9 @@
         mMccMncs = new ArrayList<>();
         in.readStringList(mMccMncs);
         mBands = new ArrayList<>();
-        in.readList(mBands, Integer.class.getClassLoader());
+        in.readList(mBands, Integer.class.getClassLoader(), java.lang.Integer.class);
         mRadioAccessSpecifiers = new ArrayList<>();
-        in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader());
+        in.readList(mRadioAccessSpecifiers, RadioAccessSpecifier.class.getClassLoader(), android.telephony.RadioAccessSpecifier.class);
     }
 
     public AvailableNetworkInfo(int subId, int priority, @NonNull List<String> mccMncs,
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 0aa4b58..29152f1 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -294,8 +294,8 @@
 
     /** @hide */
     public BarringInfo(Parcel p) {
-        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader());
-        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader());
+        mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class);
+        mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader(), android.telephony.BarringInfo.BarringServiceInfo.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index 0c258f4..b7bef39 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -53,9 +53,9 @@
     }
 
     private CallAttributes(Parcel in) {
-        this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader());
+        this.mPreciseCallState = in.readParcelable(PreciseCallState.class.getClassLoader(), android.telephony.PreciseCallState.class);
         this.mNetworkType = in.readInt();
-        this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader());
+        this.mCallQuality = in.readParcelable(CallQuality.class.getClassLoader(), android.telephony.CallQuality.class);
     }
 
     // getters
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6f92c31..53261cb 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1063,6 +1063,12 @@
             "always_show_emergency_alert_onoff_bool";
 
     /**
+     * Default mobile network MTU value, in bytes.
+     * @hide
+     */
+    public static final String KEY_DEFAULT_MTU_INT = "default_mtu_int";
+
+    /**
      * The data call retry configuration for different types of APN.
      * @hide
      */
@@ -2914,19 +2920,37 @@
             "signal_strength_nr_nsa_use_lte_as_primary_bool";
 
     /**
+     * String array of TCP buffer sizes per network type.
+     * The entries should be of the following form, with values in bytes:
+     * "network_name:read_min,read_default,read_max,write_min,write_default,write_max".
+     * For NR (5G), the following network names should be used:
+     * - NR_NSA: NR NSA, sub-6 frequencies
+     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
+     * - NR_SA: NR SA, sub-6 frequencies
+     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
+     * @hide
+     */
+    public static final String KEY_TCP_BUFFERS_STRING_ARRAY = "tcp_buffers_string_array";
+
+    /**
      * String array of default bandwidth values per network type.
-     * The entries should be of form "network_name:downstream,upstream", with values in Kbps.
+     * The entries should be of form: "network_name:downlink,uplink", with values in Kbps.
+     * For NR (5G), the following network names should be used:
+     * - NR_NSA: NR NSA, sub-6 frequencies
+     * - NR_NSA_MMWAVE: NR NSA, mmwave frequencies
+     * - NR_SA: NR SA, sub-6 frequencies
+     * - NR_SA_MMWAVE: NR SA, mmwave frequencies
      * @hide
      */
     public static final String KEY_BANDWIDTH_STRING_ARRAY = "bandwidth_string_array";
 
     /**
      * For NR (non-standalone), whether to use the LTE value instead of NR value as the default for
-     * upstream bandwidth. Downstream bandwidth will still use the NR value as the default.
+     * uplink bandwidth. Downlink bandwidth will still use the NR value as the default.
      * @hide
      */
-    public static final String KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL =
-            "bandwidth_nr_nsa_use_lte_value_for_upstream_bool";
+    public static final String KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL =
+            "bandwidth_nr_nsa_use_lte_value_for_uplink_bool";
 
     /**
      * Key identifying if voice call barring notification is required to be shown to the user.
@@ -3628,6 +3652,18 @@
     public static final String KEY_5G_WATCHDOG_TIME_MS_LONG = "5g_watchdog_time_ms_long";
 
     /**
+     * Which NR types are unmetered. A string array containing the following keys:
+     * NR_NSA - NR NSA is unmetered for sub-6 frequencies
+     * NR_NSA_MMWAVE - NR NSA is unmetered for mmwave frequencies
+     * NR_SA - NR SA is unmetered for sub-6 frequencies
+     * NR_SA_MMWAVE - NR SA is unmetered for mmwave frequencies
+     * TODO: remove other unmetered keys and replace with this
+     * @hide
+     */
+    public static final String KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY =
+            "unmetered_network_types_string_array";
+
+    /**
      * Whether NR (non-standalone) should be unmetered for all frequencies.
      * If either {@link #KEY_UNMETERED_NR_NSA_MMWAVE_BOOL} or
      * {@link #KEY_UNMETERED_NR_NSA_SUB6_BOOL} are true, then this value will be ignored.
@@ -3740,6 +3776,17 @@
             "esim_max_download_retry_attempts_int";
 
     /**
+     * List of opportunistic carrier-ids associated with CBRS Primary SIM. When CBRS pSIM is
+     * inserted, opportunistic eSIM is download and this configuration is used for grouping pSIM
+     * and opportunistic eSIM. Also when a new CBRS pSIM is inserted, old opportunistic eSIMs are
+     * deleted using the carrier-ids in this configuration.
+     *
+     * @hide
+     */
+    public static final String KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY =
+            "opportunistic_carrier_ids_int_array";
+
+    /**
      * Controls RSRP threshold at which OpportunisticNetworkService will decide whether
      * the opportunistic network is good enough for internet data.
      */
@@ -4419,6 +4466,29 @@
             "subscription_group_uuid_string";
 
     /**
+     * Controls the cellular usage setting.
+     *
+     * The usage setting indicates whether a device will remain attached to a network based on
+     * the primary use case for the service. A device will detach and search for a more-preferred
+     * network if the primary use case (voice or data) is not satisfied. Depending on the type
+     * of device, it may operate in a voice or data-centric mode by default.
+     *
+     * <p>Sets the usage setting in accordance with 3gpp 24.301 sec 4.3 and 3gpp 24.501 sec 4.3.
+     * Also refer to "UE's usage setting" as defined in 3gpp 24.301 section 3.1 and 3gpp 23.221
+     * Annex A.
+     *
+     * Either omit this key or pass a value of
+     * {@link SubscriptionManager#USAGE_SETTING_UNKNOWN unknown} to preserve the current setting.
+     *
+     * {@link SubscriptionManager#USAGE_SETTING_DEFAULT default},
+     * {@link SubscriptionManager#USAGE_SETTING_VOICE_CENTRIC voice-centric},
+     * or {@link SubscriptionManager#USAGE_SETTING_DATA_CENTRIC data-centric}.
+     * {@see SubscriptionInfo#getUsageSetting}
+     */
+    public static final String KEY_CELLULAR_USAGE_SETTING_INT =
+            "cellular_usage_setting_int";
+
+    /**
      * Data switch validation minimal gap time, in milliseconds.
      *
      * Which means, if the same subscription on the same network (based on MCC+MNC+TAC+subId)
@@ -5345,6 +5415,34 @@
     public static final String KEY_UNTHROTTLE_DATA_RETRY_WHEN_TAC_CHANGES_BOOL =
             "unthrottle_data_retry_when_tac_changes_bool";
 
+    /**
+     * IWLAN handover rules that determine whether handover is allowed or disallowed between
+     * cellular and IWLAN.
+     *
+     * The handover rules will be matched in the order. Here are some sample rules.
+     * <string-array name="iwlan_handover_rules" num="5">
+     *     <!-- Handover from IWLAN to 2G/3G is not allowed -->
+     *     <item value="source=IWLAN, target=GERAN|UTRAN, type=disallowed"/>
+     *     <!-- Handover from 2G/3G to IWLAN is not allowed -->
+     *     <item value="source=GERAN|UTRAN, target:IWLAN, type=disallowed"/>
+     *     <!-- Handover from IWLAN to 3G/4G/5G is not allowed if the device is roaming. -->
+     *     <item value="source=IWLAN, target=UTRAN|EUTRAN|NGRAN, roaming=true, type=disallowed"/>
+     *     <!-- Handover from 4G to IWLAN is not allowed -->
+     *     <item value="source=EUTRAN, target=IWLAN, type=disallowed"/>
+     *     <!-- Handover is always allowed in any condition. -->
+     *     <item value="source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN,
+     *         target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"/>
+     * </string-array>
+     *
+     * When handover is not allowed, frameworks will tear down the data network on source transport,
+     * and then setup a new one on the target transport when Qualified Network Service changes the
+     * preferred access networks for particular APN types.
+     *
+     * @hide
+     */
+    public static final String KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY =
+            "iwlan_handover_policy_string_array";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -5483,6 +5581,7 @@
 
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
+        sDefaults.putInt(KEY_DEFAULT_MTU_INT, 1500);
         sDefaults.putStringArray(KEY_CARRIER_DATA_CALL_RETRY_CONFIG_STRINGS, new String[]{
                 "default:default_randomization=2000,5000,10000,20000,40000,80000:5000,160000:5000,"
                         + "320000:5000,640000:5000,1280000:5000,1800000:5000",
@@ -5801,12 +5900,35 @@
                 CellSignalStrengthNr.USE_SSRSRP);
         sDefaults.putBoolean(KEY_SIGNAL_STRENGTH_NR_NSA_USE_LTE_AS_PRIMARY_BOOL, true);
         sDefaults.putStringArray(KEY_BANDWIDTH_STRING_ARRAY, new String[]{
-                "GPRS:24,24", "EDGE:70,18", "UMTS:115,115", "CDMA-IS95A:14,14", "CDMA-IS95B:14,14",
-                "1xRTT:30,30", "EvDo-rev.0:750,48", "EvDo-rev.A:950,550", "HSDPA:4300,620",
-                "HSUPA:4300,1800", "HSPA:4300,1800", "EvDo-rev.B:1500,550", "eHRPD:750,48",
-                "HSPAP:13000,3400", "TD-SCDMA:115,115", "LTE:30000,15000", "NR_NSA:47000,18000",
-                "NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000"});
-        sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPSTREAM_BOOL, false);
+                "GPRS:24,24", "EDGE:70,18", "UMTS:115,115", "CDMA:14,14",
+                "1xRTT:30,30", "EvDo_0:750,48", "EvDo_A:950,550", "HSDPA:4300,620",
+                "HSUPA:4300,1800", "HSPA:4300,1800", "EvDo_B:1500,550", "eHRPD:750,48",
+                "iDEN:14,14", "LTE:30000,15000", "HSPA+:13000,3400", "GSM:24,24",
+                "TD_SCDMA:115,115", "LTE_CA:30000,15000", "NR_NSA:47000,18000",
+                "NR_NSA_MMWAVE:145000,60000", "NR_SA:145000,60000", "NR_SA_MMWAVE:145000,60000"});
+        sDefaults.putStringArray(KEY_TCP_BUFFERS_STRING_ARRAY, new String[]{
+                "GPRS:4092,8760,48000,4096,8760,48000", "EDGE:4093,26280,70800,4096,16384,70800",
+                "UMTS:58254,349525,1048576,58254,349525,1048576",
+                "CDMA:4094,87380,262144,4096,16384,262144",
+                "1xRTT:16384,32768,131072,4096,16384,102400",
+                "EvDo_0:4094,87380,262144,4096,16384,262144",
+                "EvDo_A:4094,87380,262144,4096,16384,262144",
+                "HSDPA:61167,367002,1101005,8738,52429,262114",
+                "HSUPA:40778,244668,734003,16777,100663,301990",
+                "HSPA:40778,244668,734003,16777,100663,301990",
+                "EvDo_B:4094,87380,262144,4096,16384,262144",
+                "eHRPD:131072,262144,1048576,4096,16384,524288",
+                "iDEN:4094,87380,262144,4096,16384,262144",
+                "LTE:524288,1048576,2097152,262144,524288,1048576",
+                "HSPA+:122334,734003,2202010,32040,192239,576717",
+                "GSM:4092,8760,48000,4096,8760,48000",
+                "TD_SCDMA:58254,349525,1048576,58254,349525,1048576",
+                "LTE_CA:4096,6291456,12582912,4096,1048576,2097152",
+                "NR_NSA:2097152,6291456,16777216,512000,2097152,8388608",
+                "NR_NSA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608",
+                "NR_SA:2097152,6291456,16777216,512000,2097152,8388608",
+                "NR_SA_MMWAVE:2097152,6291456,16777216,512000,2097152,8388608"});
+        sDefaults.putBoolean(KEY_BANDWIDTH_NR_NSA_USE_LTE_VALUE_FOR_UPLINK_BOOL, false);
         sDefaults.putString(KEY_WCDMA_DEFAULT_SIGNAL_STRENGTH_MEASUREMENT_STRING, "rssi");
         sDefaults.putBoolean(KEY_CONFIG_SHOW_ORIG_DIAL_STRING_FOR_CDMA_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL, false);
@@ -5832,6 +5954,7 @@
         sDefaults.putInt(KEY_NR_ADVANCED_CAPABLE_PCO_ID_INT, 0);
         sDefaults.putBoolean(KEY_ENABLE_NR_ADVANCED_WHILE_ROAMING_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL, false);
+        sDefaults.putStringArray(KEY_UNMETERED_NETWORK_TYPES_STRING_ARRAY, new String[0]);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_MMWAVE_BOOL, false);
         sDefaults.putBoolean(KEY_UNMETERED_NR_NSA_SUB6_BOOL, false);
@@ -5845,6 +5968,7 @@
         sDefaults.putString(KEY_SMDP_SERVER_ADDRESS_STRING, "");
         sDefaults.putInt(KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT, 5);
         sDefaults.putInt(KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT, 60);
+        sDefaults.putIntArray(KEY_OPPORTUNISTIC_CARRIER_IDS_INT_ARRAY, new int[] {0});
         /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */
         sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108);
         /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */
@@ -5892,6 +6016,7 @@
         sDefaults.putInt(
                 KEY_OPPORTUNISTIC_TIME_TO_SCAN_AFTER_CAPABILITY_SWITCH_TO_PRIMARY_LONG,
                 120000);
+        sDefaults.putAll(ImsServiceEntitlement.getDefaults());
         sDefaults.putAll(Gps.getDefaults());
         sDefaults.putIntArray(KEY_CDMA_ENHANCED_ROAMING_INDICATOR_FOR_HOME_NETWORK_INT_ARRAY,
                 new int[] {
@@ -5982,6 +6107,11 @@
         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.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
+                "source=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, "
+                        + "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
+        sDefaults.putInt(KEY_CELLULAR_USAGE_SETTING_INT,
+                SubscriptionManager.USAGE_SETTING_UNKNOWN);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 4db00cf..b4b8aee 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -379,7 +379,7 @@
         mBands = in.createIntArray();
         mBandwidth = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null);
+        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 13d9373..90e6295 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -297,7 +297,7 @@
         mCpid = in.readInt();
         mUarfcn = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null);
+        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 9b463da..72282cd 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -313,7 +313,7 @@
         mPsc = in.readInt();
         mUarfcn = in.readInt();
         mAdditionalPlmns = (ArraySet<String>) in.readArraySet(null);
-        mCsgInfo = in.readParcelable(null);
+        mCsgInfo = in.readParcelable(null, android.telephony.ClosedSubscriberGroupInfo.class);
 
         updateGlobalCellId();
         if (DBG) log(toString());
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index cd22abd..f5ba3ab 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -326,7 +326,7 @@
         mCsiRsrq = in.readInt();
         mCsiSinr = in.readInt();
         mCsiCqiTableIndex = in.readInt();
-        mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader());
+        mCsiCqiReport = in.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
         mSsRsrp = in.readInt();
         mSsRsrq = in.readInt();
         mSsSinr = in.readInt();
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 957f683..837124f 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -105,7 +105,7 @@
         isDcNrRestricted = source.readBoolean();
         isNrAvailable = source.readBoolean();
         isEnDcAvailable = source.readBoolean();
-        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader());
+        mVopsSupportInfo = source.readParcelable(VopsSupportInfo.class.getClassLoader(), android.telephony.VopsSupportInfo.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 6a80766..c18443e 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -311,12 +311,12 @@
         mRejectCause = source.readInt();
         mEmergencyOnly = source.readBoolean();
         mAvailableServices = new ArrayList<>();
-        source.readList(mAvailableServices, Integer.class.getClassLoader());
-        mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader());
+        source.readList(mAvailableServices, Integer.class.getClassLoader(), java.lang.Integer.class);
+        mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader(), android.telephony.CellIdentity.class);
         mVoiceSpecificInfo = source.readParcelable(
-                VoiceSpecificRegistrationInfo.class.getClassLoader());
+                VoiceSpecificRegistrationInfo.class.getClassLoader(), android.telephony.VoiceSpecificRegistrationInfo.class);
         mDataSpecificInfo = source.readParcelable(
-                DataSpecificRegistrationInfo.class.getClassLoader());
+                DataSpecificRegistrationInfo.class.getClassLoader(), android.telephony.DataSpecificRegistrationInfo.class);
         mNrState = source.readInt();
         mRplmn = source.readString();
         mIsUsingCarrierAggregation = source.readBoolean();
diff --git a/telephony/java/android/telephony/PhoneCapability.java b/telephony/java/android/telephony/PhoneCapability.java
index a3aaf61..63e3468 100644
--- a/telephony/java/android/telephony/PhoneCapability.java
+++ b/telephony/java/android/telephony/PhoneCapability.java
@@ -150,7 +150,7 @@
         mMaxActiveDataSubscriptions = in.readInt();
         mNetworkValidationBeforeSwitchSupported = in.readBoolean();
         mLogicalModemList = new ArrayList<>();
-        in.readList(mLogicalModemList, ModemInfo.class.getClassLoader());
+        in.readList(mLogicalModemList, ModemInfo.class.getClassLoader(), android.telephony.ModemInfo.class);
         mDeviceNrCapabilities = in.createIntArray();
     }
 
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index ce2f3f9..2670b03 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -125,9 +125,9 @@
         mId = in.readInt();
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader());
+        mLinkProperties = in.readParcelable(LinkProperties.class.getClassLoader(), android.net.LinkProperties.class);
         mFailCause = in.readInt();
-        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader());
+        mApnSetting = in.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 5affb62..70da9b9 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -479,7 +479,7 @@
         mIsEmergencyOnly = in.readInt() != 0;
         mArfcnRsrpBoost = in.readInt();
         synchronized (mNetworkRegistrationInfos) {
-            in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader());
+            in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader(), android.telephony.NetworkRegistrationInfo.class);
         }
         mChannelNumber = in.readInt();
         mCellBandwidths = in.createIntArray();
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index b7bc467..f74ef0f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -275,12 +275,12 @@
     public SignalStrength(Parcel in) {
         if (DBG) log("Size of signalstrength parcel:" + in.dataSize());
 
-        mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader());
-        mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader());
-        mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader());
-        mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader());
-        mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
-        mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader());
+        mCdma = in.readParcelable(CellSignalStrengthCdma.class.getClassLoader(), android.telephony.CellSignalStrengthCdma.class);
+        mGsm = in.readParcelable(CellSignalStrengthGsm.class.getClassLoader(), android.telephony.CellSignalStrengthGsm.class);
+        mWcdma = in.readParcelable(CellSignalStrengthWcdma.class.getClassLoader(), android.telephony.CellSignalStrengthWcdma.class);
+        mTdscdma = in.readParcelable(CellSignalStrengthTdscdma.class.getClassLoader(), android.telephony.CellSignalStrengthTdscdma.class);
+        mLte = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthLte.class);
+        mNr = in.readParcelable(CellSignalStrengthLte.class.getClassLoader(), android.telephony.CellSignalStrengthNr.class);
         mTimestampMillis = in.readLong();
     }
 
diff --git a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
index 9cb80f1..4884d54 100644
--- a/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
+++ b/telephony/java/android/telephony/SignalStrengthUpdateRequest.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -130,7 +131,10 @@
         /**
          * Set the builder object if require reporting on the system thresholds when device is idle.
          *
-         * <p>This can only used by the system caller. Requires permission
+         * <p>This is intended to be used by the system privileged caller only. When setting to
+         * {@code true}, signal strength update request through
+         * {@link TelephonyManager#setSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)}
+         * will require permission
          * {@link android.Manifest.permission#LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH}.
          *
          * @param isSystemThresholdReportingRequestedWhileIdle true if request reporting on the
@@ -138,6 +142,7 @@
          * @return the builder to facilitate the chaining
          * @hide
          */
+        @SystemApi
         @RequiresPermission(android.Manifest.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH)
         public @NonNull Builder setSystemThresholdReportingRequestedWhileIdle(
                 boolean isSystemThresholdReportingRequestedWhileIdle) {
@@ -191,6 +196,7 @@
      *
      * @hide
      */
+    @SystemApi
     public boolean isSystemThresholdReportingRequestedWhileIdle() {
         return mIsSystemThresholdReportingRequestedWhileIdle;
     }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index d11ad91..c36eb2f 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -37,6 +37,7 @@
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
+import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -227,6 +228,11 @@
     private final int mPortIndex;
 
     /**
+     * Subscription's preferred usage setting.
+     */
+    private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
+
+    /**
      * Public copy constructor.
      * @hide
      */
@@ -284,6 +290,7 @@
                 cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierId, profileClass,
                 subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, 0);
     }
+
     /**
      * @hide
      */
@@ -295,6 +302,24 @@
             int carrierId, int profileClass, int subType, @Nullable String groupOwner,
             @Nullable UiccAccessRule[] carrierConfigAccessRules,
             boolean areUiccApplicationsEnabled, int portIndex) {
+        this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
+                roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString,
+                cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierId, profileClass,
+                subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
+                portIndex, SubscriptionManager.USAGE_SETTING_DEFAULT);
+    }
+
+    /**
+     * @hide
+     */
+    public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
+            CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
+            Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
+            @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
+            boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+            int carrierId, int profileClass, int subType, @Nullable String groupOwner,
+            @Nullable UiccAccessRule[] carrierConfigAccessRules,
+            boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -322,6 +347,7 @@
         this.mCarrierConfigAccessRules = carrierConfigAccessRules;
         this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
         this.mPortIndex = portIndex;
+        this.mUsageSetting = usageSetting;
     }
     /**
      * @return the subscription ID.
@@ -796,7 +822,18 @@
         return mAreUiccApplicationsEnabled;
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<SubscriptionInfo> CREATOR = new Parcelable.Creator<SubscriptionInfo>() {
+    /**
+     * Get the usage setting for this subscription.
+     *
+     * @return the usage setting used for this subscription.
+     */
+    public @UsageSetting int getUsageSetting() {
+        return mUsageSetting;
+    }
+
+    public static final @android.annotation.NonNull
+            Parcelable.Creator<SubscriptionInfo> CREATOR =
+                    new Parcelable.Creator<SubscriptionInfo>() {
         @Override
         public SubscriptionInfo createFromParcel(Parcel source) {
             int id = source.readInt();
@@ -828,12 +865,14 @@
             UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
                 UiccAccessRule.CREATOR);
             boolean areUiccApplicationsEnabled = source.readBoolean();
+            int usageSetting = source.readInt();
 
             SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
                     carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
                     mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
                     isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
-                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, portId);
+                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
+                    portId, usageSetting);
             info.setAssociatedPlmns(ehplmns, hplmns);
             return info;
         }
@@ -875,6 +914,7 @@
         dest.writeString(mGroupOwner);
         dest.writeTypedArray(mCarrierConfigAccessRules, flags);
         dest.writeBoolean(mAreUiccApplicationsEnabled);
+        dest.writeInt(mUsageSetting);
     }
 
     @Override
@@ -919,7 +959,8 @@
                 + " subscriptionType=" + mSubscriptionType
                 + " groupOwner=" + mGroupOwner
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
-                + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled + "}";
+                + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
+                + " usageSetting=" + mUsageSetting + "}";
     }
 
     @Override
@@ -927,7 +968,8 @@
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
                 mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
                 mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled,
-                mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex);
+                mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex,
+                mUsageSetting);
     }
 
     @Override
@@ -967,6 +1009,7 @@
                 && Arrays.equals(mNativeAccessRules, toCompare.mNativeAccessRules)
                 && mProfileClass == toCompare.mProfileClass
                 && Arrays.equals(mEhplmns, toCompare.mEhplmns)
-                && Arrays.equals(mHplmns, toCompare.mHplmns);
+                && Arrays.equals(mHplmns, toCompare.mHplmns)
+                && mUsageSetting == toCompare.mUsageSetting;
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1fab89e..4e6a7a3 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -941,6 +941,13 @@
     public static final String PROFILE_CLASS = SimInfo.COLUMN_PROFILE_CLASS;
 
     /**
+     * TelephonyProvider column name for the port index of the active UICC port.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String PORT_INDEX = SimInfo.COLUMN_PORT_INDEX;
+
+    /**
      * TelephonyProvider column name for VoIMS opt-in status.
      *
      * <P>Type: INTEGER (int)</P>
@@ -1030,12 +1037,75 @@
     public static final String UICC_APPLICATIONS_ENABLED = SimInfo.COLUMN_UICC_APPLICATIONS_ENABLED;
 
     /**
-     * Indicate which network type is allowed. By default it's enabled.
+     * Indicate which network type is allowed.
      * @hide
      */
     public static final String ALLOWED_NETWORK_TYPES =
             SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"USAGE_SETTING_"},
+        value = {
+            USAGE_SETTING_UNKNOWN,
+            USAGE_SETTING_DEFAULT,
+            USAGE_SETTING_VOICE_CENTRIC,
+            USAGE_SETTING_DATA_CENTRIC})
+    public @interface UsageSetting {}
+
+    /**
+     * The usage setting is unknown.
+     *
+     * This will be the usage setting returned on devices that do not support querying the
+     * or setting the usage setting.
+     *
+     * It may also be provided by a carrier that wishes to provide a value to avoid making any
+     * settings changes.
+     */
+    public static final int USAGE_SETTING_UNKNOWN = -1;
+
+    /**
+     * Subscription uses the default setting.
+     *
+     * The value is based upon device capability and the other properties of the subscription.
+     *
+     * Most subscriptions will default to voice-centric when in a phone.
+     *
+     * An opportunistic subscription will default to data-centric.
+     *
+     * {@see SubscriptionInfo#isOpportunistic}
+     */
+    public static final int USAGE_SETTING_DEFAULT = 0;
+
+    /**
+     * This subscription is forced to voice-centric mode
+     *
+     * <p>Refer to voice-centric mode in 3gpp 24.301 sec 4.3 and 3gpp 24.501 sec 4.3.
+     * Also refer to "UE's usage setting" as defined in 3gpp 24.301 section 3.1 and 3gpp 23.221
+     * Annex A.
+     */
+    public static final int USAGE_SETTING_VOICE_CENTRIC = 1;
+
+    /**
+     * This subscription is forced to data-centric mode
+     *
+     * <p>Refer to data-centric mode in 3gpp 24.301 sec 4.3 and 3gpp 24.501 sec 4.3.
+     * Also refer to "UE's usage setting" as defined in 3gpp 24.301 section 3.1 and 3gpp 23.221
+     * Annex A.
+     */
+    public static final int USAGE_SETTING_DATA_CENTRIC = 2;
+
+    /**
+     * Indicate the preferred usage setting for the subscription.
+     *
+     * 0 - Default - If the value has not been explicitly set, it will be "default"
+     * 1 - Voice-centric
+     * 2 - Data-centric
+     *
+     * @hide
+     */
+    public static final String USAGE_SETTING = SimInfo.COLUMN_USAGE_SETTING;
+
     /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
@@ -3811,14 +3881,21 @@
     }
 
     /**
-     * Returns the phone number for the given {@code subId} and {@code source},
+     * Returns the phone number for the given {@code subscriptionId} and {@code source},
      * or an empty string if not available.
      *
-     * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, or
-     * READ_PRIVILEGED_PHONE_STATE permission (can only be granted to apps preloaded on device),
-     * or that the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
+     * <p>General apps that need to know the phone number should use {@link #getPhoneNumber(int)}
+     * instead. This API may be suitable specific apps that needs to know the phone number from
+     * a specific source. For example, a carrier app needs to know exactly what's on
+     * {@link #PHONE_NUMBER_SOURCE_UICC UICC} and decide if the previously set phone number
+     * of source {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} should be updated.
+     *
+     * <p>Note the assumption is that one subscription (which usually means one SIM) has
+     * only one phone number. The multiple sources backup each other so hopefully at least one
+     * is availavle. For example, for a carrier that doesn't typically set phone numbers
+     * on {@link #PHONE_NUMBER_SOURCE_UICC UICC}, the source {@link #PHONE_NUMBER_SOURCE_IMS IMS}
+     * may provide one. Or, a carrier may decide to provide the phone number via source
+     * {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} if neither source UICC nor IMS is available.
      *
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
      *                       for the default one.
@@ -3831,10 +3908,10 @@
      * @see #PHONE_NUMBER_SOURCE_CARRIER
      * @see #PHONE_NUMBER_SOURCE_IMS
      */
-    @SuppressAutoDoc // No support for carrier privileges (b/72967236)
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PHONE_NUMBERS,
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
     })
     @NonNull
     public String getPhoneNumber(int subscriptionId, @PhoneNumberSource int source) {
@@ -3863,16 +3940,14 @@
      * Returns the phone number for the given {@code subId}, or an empty string if
      * not available.
      *
+     * <p>This API is suitable for general apps that needs to know the phone number.
+     * For specific apps that needs to know the phone number provided by a specific source,
+     * {@link #getPhoneNumber(int, int)} may be suitable.
+     *
      * <p>This API is built up on {@link #getPhoneNumber(int, int)}, but picks
      * from available sources in the following order: {@link #PHONE_NUMBER_SOURCE_CARRIER}
      * > {@link #PHONE_NUMBER_SOURCE_UICC} > {@link #PHONE_NUMBER_SOURCE_IMS}.
      *
-     * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, or
-     * READ_PRIVILEGED_PHONE_STATE permission (can only be granted to apps preloaded on device),
-     * or that the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
-     *
      * @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.
@@ -3880,10 +3955,10 @@
      * @throws SecurityException if the caller doesn't have permissions required.
      * @see #getPhoneNumber(int, int)
      */
-    @SuppressAutoDoc // No support for carrier privileges (b/72967236)
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PHONE_NUMBERS,
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            "carrier privileges",
     })
     @NonNull
     public String getPhoneNumber(int subscriptionId) {
@@ -3908,8 +3983,8 @@
      * {@link #PHONE_NUMBER_SOURCE_CARRIER carrier}.
      * Sets an empty string to remove the previously set phone number.
      *
-     * <p>Requires Permission: the calling app has carrier privileges
-     * (see {@link TelephonyManager#hasCarrierPrivileges}).
+     * <p>The API is suitable for carrier apps to provide a phone number, for example when
+     * it's not possible to update {@link #PHONE_NUMBER_SOURCE_UICC UICC} directly.
      *
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
      *                       for the default one.
@@ -3918,6 +3993,7 @@
      * @throws NullPointerException if {@code number} is {@code null}.
      * @throws SecurityException if the caller doesn't have permissions required.
      */
+    @RequiresPermission("carrier privileges")
     public void setCarrierPhoneNumber(int subscriptionId, @NonNull String number) {
         if (subscriptionId == DEFAULT_SUBSCRIPTION_ID) {
             subscriptionId = getDefaultSubscriptionId();
@@ -3937,4 +4013,33 @@
             throw ex.rethrowAsRuntimeException();
         }
     }
+
+    /**
+     * Set the preferred usage setting.
+     *
+     * The cellular usage setting is a switch which controls the mode of operation for the cellular
+     * radio to either require or not require voice service. It is not managed via Android’s
+     * Settings.
+     *
+     * @param subscriptionId the subId of the subscription.
+     * @param usageSetting the requested usage setting.
+     *
+     * @throws IllegalStateException if a specific mode or setting the mode is not supported on a
+     * particular device.
+     *
+     * <p>Requires {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * or that the calling app has CarrierPrivileges for the given subscription.
+     *
+     * Note: This method will not allow the setting of USAGE_SETTING_UNKNOWN.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    void setUsageSetting(int subscriptionId, @UsageSetting int usageSetting) {
+        if (VDBG) logd("[setUsageSetting]+ setting:" + usageSetting + " subId:" + subscriptionId);
+        setSubscriptionPropertyHelper(subscriptionId, "setUsageSetting",
+                (iSub)-> iSub.setUsageSetting(
+                        usageSetting, subscriptionId, mContext.getOpPackageName()));
+    }
 }
+
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d120f5a..2d16f9e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -70,6 +70,7 @@
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.carrier.CarrierIdentifier;
+import android.service.carrier.CarrierService;
 import android.sysprop.TelephonyProperties;
 import android.telecom.CallScreeningService;
 import android.telecom.InCallService;
@@ -117,6 +118,7 @@
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.IUpdateAvailableNetworksCallback;
+import com.android.internal.telephony.IccLogicalChannelRequest;
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
@@ -319,11 +321,19 @@
     public static final int UNINITIALIZED_CARD_ID = -2;
 
     /**
-     * Default port index for the UICC Card
-     * @hide
+     * Default port index for a UICC.
+     *
+     * On physical SIM cards the only available port is 0.
+     * See {@link android.telephony.UiccPortInfo} for more information on ports.
+     *
+     * See {@link android.telephony.euicc.EuiccManager#isSimPortAvailable(int)} for information on
+     * how portIndex is used on eUICCs.
      */
     public static final int DEFAULT_PORT_INDEX = 0;
 
+    /** @hide */
+    public static final int INVALID_PORT_INDEX = -1;
+
     private final Context mContext;
     private final int mSubId;
     @UnsupportedAppUsage
@@ -1898,9 +1908,10 @@
      * the IMEI/SV for GSM phones. Return null if the software version is
      * not available.
      * <p>
-     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}.
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE})
     @Nullable
     public String getDeviceSoftwareVersion() {
         return getDeviceSoftwareVersion(getSlotIndex());
@@ -2962,7 +2973,9 @@
      * when opportunistic network is providing cellular internet connection to the user.
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
-     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * or {@link android.Manifest.permission#READ_BASIC_PHONE_STATE
+     * READ_BASIC_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link #hasCarrierPrivileges}).
      *
      * @return the network type
      *
@@ -2985,7 +2998,9 @@
      * @see #NETWORK_TYPE_NR
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE})
     public @NetworkType int getDataNetworkType() {
         return getDataNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
     }
@@ -3023,10 +3038,14 @@
      * Returns the NETWORK_TYPE_xxxx for voice
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
-     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     * or {@link android.Manifest.permission#READ_BASIC_PHONE_STATE
+     * READ_BASIC_PHONE_STATE} or that the calling app has carrier privileges
+     * (see {@link #hasCarrierPrivileges}).
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE})
     public @NetworkType int getVoiceNetworkType() {
         return getVoiceNetworkType(getSubId());
     }
@@ -4142,28 +4161,28 @@
      * 0) or
      * second physical slot(value 1), port (index 0), while the other physical slot remains unmapped
      * and inactive.
-     * slotMapping[0] = UiccSlotMapping{0 //logical slot, 0 //physical slot//, 0 //port//}
-     * slotMapping[0] = UiccSlotMapping{1 // logical slot, 1 //physical slot//, 0 //port//}
+     * slotMapping[0] = UiccSlotMapping{0 //port index, 0 //physical slot, 0 //logical slot} or
+     * slotMapping[0] = UiccSlotMapping{0 //port index, 1 //physical slot, 0 //logical slot}
      *
      * Example no. of logical slots 2 and physical slots 2 supports MEP with 2 ports available:
      * Each logical slot must be mapped to a port (physical slot and port combination).
      * First logical slot (index 0) can be mapped to physical slot 1 and the second logical slot
      * can be mapped to either port from physical slot 2.
      *
-     * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 0, 0} or
+     * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{0, 1, 1} or
      * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1}
      *
      * or the other way around, the second logical slot(index 1) can be mapped to physical slot 1
      * and the first logical slot can be mapped to either port from physical slot 2.
      *
-     * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{1, 0, 0} or
+     * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{0, 1, 1} or
      * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{1, 1, 1}
      *
      * another possible mapping is each logical slot maps to each port of physical slot 2 and there
      * is no active logical modem mapped to physical slot 1.
      *
-     * slotMapping[0] = UiccSlotMapping{1, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1} or
-     * slotMapping[0] = UiccSlotMapping{1, 1, 1} and slotMapping[1] = UiccSlotMapping{1, 0, 0}
+     * slotMapping[0] = UiccSlotMapping{0, 1, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1} or
+     * slotMapping[0] = UiccSlotMapping{1, 1, 0} and slotMapping[1] = UiccSlotMapping{0, 1, 1}
      *
      * @param slotMapping Logical to physical slot and port mapping.
      * @throws IllegalStateException if telephony service is null or slot mapping was sent when the
@@ -4240,7 +4259,7 @@
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @NonNull
     public Collection<UiccSlotMapping> getSimSlotMapping() {
-        List<UiccSlotMapping> slotMap = new ArrayList<>();
+        List<UiccSlotMapping> slotMap;
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
@@ -6756,17 +6775,24 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
+     * @deprecated instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
+    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int slotIndex,
             @Nullable String aid, int p2) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.iccOpenLogicalChannelBySlot(slotIndex, getOpPackageName(), aid,
-                        p2);
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.slotIndex = slotIndex;
+                request.aid = aid;
+                request.p2 = p2;
+                request.callingPackage = getOpPackageName();
+                request.binder = new Binder();
+                return telephony.iccOpenLogicalChannel(request);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
@@ -6775,6 +6801,58 @@
     }
 
     /**
+     * Opens a logical channel to the ICC card using the physical slot index and port index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index and port index.
+     *
+     * This operation wraps two APDU instructions:
+     * <ul>
+     *     <li>MANAGE CHANNEL to open a logical channel</li>
+     *     <li>SELECT the given {@code AID} using the given {@code p2}</li>
+     * </ul>
+     *
+     * Per Open Mobile API Specification v3.2 section 6.2.7.h, only p2 values of 0x00, 0x04, 0x08,
+     * and 0x0C are guaranteed to be supported.
+     *
+     * If the SELECT command's status word is not '9000', '62xx', or '63xx', the status word will be
+     * considered an error and the channel shall not be opened.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHO command.
+     *
+     * @param slotIndex the physical slot index of the ICC card
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param aid Application id. See ETSI 102.221 and 101.220.
+     * @param p2 P2 parameter (described in ISO 7816-4).
+     * @return an IccOpenLogicalChannelResponse object.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(int slotIndex,
+            int portIndex, @Nullable String aid, int p2) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.slotIndex = slotIndex;
+                request.portIndex = portIndex;
+                request.aid = aid;
+                request.p2 = p2;
+                request.callingPackage = getOpPackageName();
+                request.binder = new Binder();
+                return telephony.iccOpenLogicalChannel(request);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Opens a logical channel to the ICC card.
      *
      * This operation wraps two APDU instructions:
@@ -6798,12 +6876,7 @@
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openLogicalChannel(byte[], byte)}.
      */
-    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
         return iccOpenLogicalChannel(getSubId(), AID, p2);
     }
@@ -6834,17 +6907,19 @@
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openLogicalChannel(byte[], byte)}.
      */
-    @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.iccOpenLogicalChannel(subId, getOpPackageName(), AID, p2);
+            if (telephony != null) {
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.subId = subId;
+                request.callingPackage = getOpPackageName();
+                request.aid = AID;
+                request.p2 = p2;
+                request.binder = new Binder();
+                return telephony.iccOpenLogicalChannel(request);
+            }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -6867,17 +6942,19 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.Channel#close()}.
+     * @deprecated instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
      */
-    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
+    @Deprecated
     public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.iccCloseLogicalChannelBySlot(slotIndex, channel);
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.slotIndex = slotIndex;
+                request.channel = channel;
+                return telephony.iccCloseLogicalChannel(request);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
@@ -6886,6 +6963,45 @@
     }
 
     /**
+     * Closes a previously opened logical channel to the ICC card using the physical slot index and
+     * port index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index and port index.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CCHC command.
+     *
+     * @param slotIndex the physical slot index of the ICC card
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param channel is the channel id to be closed as returned by a successful
+     *            iccOpenLogicalChannel.
+     *
+     * @throws IllegalStateException if the Telephony process is not currently available or modem
+     *                               currently can't process this command.
+     * @throws IllegalArgumentException if invalid arguments are passed.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    public void iccCloseLogicalChannelByPort(int slotIndex, int portIndex, int channel) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.slotIndex = slotIndex;
+                request.portIndex = portIndex;
+                request.channel = channel;
+                telephony.iccCloseLogicalChannel(request);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Closes a previously opened logical channel to the ICC card.
      *
      * Input parameters equivalent to TS 27.007 AT+CCHC command.
@@ -6897,10 +7013,7 @@
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.Channel#close()}.
      */
-    @Deprecated
     public boolean iccCloseLogicalChannel(int channel) {
         return iccCloseLogicalChannel(getSubId(), channel);
     }
@@ -6919,15 +7032,16 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.Channel#close()}.
      */
-    @Deprecated
     public boolean iccCloseLogicalChannel(int subId, int channel) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null)
-                return telephony.iccCloseLogicalChannel(subId, channel);
+            if (telephony != null) {
+                IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+                request.subId = subId;
+                request.channel = channel;
+                return telephony.iccCloseLogicalChannel(request);
+            }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
         }
@@ -6958,20 +7072,20 @@
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
+     * @deprecated instead use
+     * {@link #iccTransmitApduLogicalChannelByPort(int, int, int, int, int, int, int, int, String)}
      */
-    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @Nullable
+    @Deprecated
     public String iccTransmitApduLogicalChannelBySlot(int slotIndex, int channel, int cla,
             int instruction, int p1, int p2, int p3, @Nullable String data) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.iccTransmitApduLogicalChannelBySlot(slotIndex, channel, cla,
-                        instruction, p1, p2, p3, data);
+                return telephony.iccTransmitApduLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
+                         channel, cla, instruction, p1, p2, p3, data);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
@@ -6980,6 +7094,50 @@
     }
 
     /**
+     * Transmit an APDU to the ICC card over a logical channel using the physical slot index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CGLA command.
+     *
+     * @param slotIndex the physical slot index of the ICC card
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param channel is the channel id to be closed as returned by a successful
+     *            iccOpenLogicalChannel.
+     * @param cla Class of the APDU command.
+     * @param instruction Instruction of the APDU command.
+     * @param p1 P1 value of the APDU command.
+     * @param p2 P2 value of the APDU command.
+     * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
+     *            is sent to the SIM.
+     * @param data Data to be sent with the APDU.
+     * @return The APDU response from the ICC card with the status appended at the end, or null if
+     * there is an issue connecting to the Telephony service.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public String iccTransmitApduLogicalChannelByPort(int slotIndex, int portIndex, int channel,
+            int cla, int instruction, int p1, int p2, int p3, @Nullable String data) {
+        String response;
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                response = telephony.iccTransmitApduLogicalChannelByPort(slotIndex, portIndex,
+                        channel, cla, instruction, p1, p2, p3, data);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+        return response;
+    }
+
+    /**
      * Transmit an APDU to the ICC card over a logical channel.
      *
      * Input parameters equivalent to TS 27.007 AT+CGLA command.
@@ -6999,10 +7157,7 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public String iccTransmitApduLogicalChannel(int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduLogicalChannel(getSubId(), channel, cla,
@@ -7031,10 +7186,7 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -7070,23 +7222,20 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
+     * @deprecated instead use
+     * {@link #iccTransmitApduBasicChannelByPort(int, int, int, int, int, int, int, String)}
      */
-    @Deprecated
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @SystemApi
     @NonNull
+    @Deprecated
     public String iccTransmitApduBasicChannelBySlot(int slotIndex, int cla, int instruction, int p1,
             int p2, int p3, @Nullable String data) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.iccTransmitApduBasicChannelBySlot(slotIndex, getOpPackageName(),
-                        cla, instruction, p1, p2, p3, data);
+                return telephony.iccTransmitApduBasicChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
+                         getOpPackageName(), cla, instruction, p1, p2, p3, data);
             }
         } catch (RemoteException ex) {
         } catch (NullPointerException ex) {
@@ -7095,6 +7244,47 @@
     }
 
     /**
+     * Transmit an APDU to the ICC card over the basic channel using the physical slot index.
+     *
+     * Use this method when no subscriptions are available on the SIM and the operation must be
+     * performed using the physical slot index.
+     *
+     * Input parameters equivalent to TS 27.007 AT+CSIM command.
+     *
+     * @param slotIndex the physical slot index of the ICC card to target
+     * @param portIndex The port index is an enumeration of the ports available on the UICC.
+     *                  Use {@link UiccPortInfo#getPortIndex()} to get portIndex.
+     * @param cla Class of the APDU command.
+     * @param instruction Instruction of the APDU command.
+     * @param p1 P1 value of the APDU command.
+     * @param p2 P2 value of the APDU command.
+     * @param p3 P3 value of the APDU command. If p3 is negative a 4 byte APDU
+     *            is sent to the SIM.
+     * @param data Data to be sent with the APDU.
+     * @return The APDU response from the ICC card with the status appended at
+     *            the end.
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @SystemApi
+    @NonNull
+    public String iccTransmitApduBasicChannelByPort(int slotIndex, int portIndex, int cla,
+            int instruction, int p1, int p2, int p3, @Nullable String data) {
+        String response;
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                response = telephony.iccTransmitApduBasicChannelByPort(slotIndex, portIndex,
+                        getOpPackageName(), cla, instruction, p1, p2, p3, data);
+            } else {
+                throw new IllegalStateException("telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowAsRuntimeException();
+        }
+        return response;
+    }
+    /**
      * Transmit an APDU to the ICC card over the basic channel.
      *
      * Input parameters equivalent to TS 27.007 AT+CSIM command.
@@ -7112,13 +7302,7 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public String iccTransmitApduBasicChannel(int cla,
             int instruction, int p1, int p2, int p3, String data) {
         return iccTransmitApduBasicChannel(getSubId(), cla,
@@ -7145,13 +7329,7 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public String iccTransmitApduBasicChannel(int subId, int cla,
             int instruction, int p1, int p2, int p3, String data) {
         try {
@@ -7179,13 +7357,7 @@
      * @param p3 P3 value of the APDU command.
      * @param filePath
      * @return The APDU response.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
             String filePath) {
         return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath);
@@ -7207,13 +7379,7 @@
      * @param filePath
      * @return The APDU response.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
             int p3, String filePath) {
         try {
@@ -7239,13 +7405,7 @@
      * @return The APDU response from the ICC card in hexadecimal format
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public String sendEnvelopeWithStatus(String content) {
         return sendEnvelopeWithStatus(getSubId(), content);
     }
@@ -7265,13 +7425,7 @@
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
      * @hide
-     * @deprecated Use {@link android.se.omapi.SEService} APIs instead. See
-     *             {@link android.se.omapi.SEService#getUiccReader(int)},
-     *             {@link android.se.omapi.Reader#openSession()},
-     *             {@link android.se.omapi.Session#openBasicChannel(byte[], byte)},
-     *             {@link android.se.omapi.Channel#transmit(byte[])}.
      */
-    @Deprecated
     public String sendEnvelopeWithStatus(int subId, String content) {
         try {
             ITelephony telephony = getITelephony();
@@ -8592,12 +8746,58 @@
     public NetworkScan requestNetworkScan(
             NetworkScanRequest request, Executor executor,
             TelephonyScanManager.NetworkScanCallback callback) {
-        synchronized (this) {
+        return requestNetworkScan(false, request, executor, callback);
+    }
+
+    /**
+     * Request a network scan.
+     *
+     * This method is asynchronous, so the network scan results will be returned by callback.
+     * The returned NetworkScan will contain a callback method which can be used to stop the scan.
+     *
+     * <p>Requires Permission:
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
+     * app has carrier privileges (see {@link #hasCarrierPrivileges})
+     * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
+     *
+     * If the system-wide location switch is off, apps may still call this API, with the
+     * following constraints:
+     * <ol>
+     *     <li>The app must hold the {@code android.permission.NETWORK_SCAN} permission.</li>
+     *     <li>The app must not supply any specific bands or channels to scan.</li>
+     *     <li>The app must only specify MCC/MNC pairs that are
+     *     associated to a SIM in the device.</li>
+     *     <li>Returned results will have no meaningful info other than signal strength
+     *     and MCC/MNC info.</li>
+     * </ol>
+     *
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+     * location related information which will be sent if the caller already possess
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
+     * @param request Contains all the RAT with bands/channels that need to be scanned.
+     * @param executor The executor through which the callback should be invoked. Since the scan
+     *        request may trigger multiple callbacks and they must be invoked in the same order as
+     *        they are received by the platform, the user should provide an executor which executes
+     *        tasks one at a time in serial order. For example AsyncTask.SERIAL_EXECUTOR.
+     * @param callback Returns network scan results or errors.
+     * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
+     */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+            Manifest.permission.ACCESS_FINE_LOCATION
+    })
+    public @Nullable NetworkScan requestNetworkScan(
+            boolean renounceFineLocationAccess, @NonNull NetworkScanRequest request,
+            @NonNull Executor executor,
+            @NonNull TelephonyScanManager.NetworkScanCallback callback) {
+        synchronized (sCacheLock) {
             if (mTelephonyScanManager == null) {
                 mTelephonyScanManager = new TelephonyScanManager();
             }
         }
-        return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback,
+        return mTelephonyScanManager.requestNetworkScan(getSubId(), renounceFineLocationAccess,
+                request, executor, callback,
                 getOpPackageName(), getAttributionTag());
     }
 
@@ -9403,6 +9603,57 @@
         return null;
     }
 
+    /**
+     * Returns the package name that provides the {@link CarrierService} implementation for the
+     * current subscription, or {@code null} if no package with carrier privileges declares one.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, then the provided
+     * subscription ID is used. Otherwise, the default subscription ID will be used.
+     *
+     * @return The system-selected package that provides the {@link CarrierService} implementation
+     * for the current subscription, or {@code null} if none is resolved
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @Nullable String getCarrierServicePackageName() {
+        // TODO(b/205736323) plumb this through to CarrierPrivilegesTracker, which will cache the
+        // value instead of re-querying every time.
+        List<String> carrierServicePackages =
+                getCarrierPackageNamesForIntent(
+                        new Intent(CarrierService.CARRIER_SERVICE_INTERFACE));
+        if (carrierServicePackages != null && !carrierServicePackages.isEmpty()) {
+            return carrierServicePackages.get(0);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the package name that provides the {@link CarrierService} implementation for the
+     * specified {@code logicalSlotIndex}, or {@code null} if no package with carrier privileges
+     * declares one.
+     *
+     * @param logicalSlotIndex The slot index to fetch the {@link CarrierService} package for
+     * @return The system-selected package that provides the {@link CarrierService} implementation
+     * for the slot, or {@code null} if none is resolved
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @Nullable String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
+        // TODO(b/205736323) plumb this through to CarrierPrivilegesTracker, which will cache the
+        // value instead of re-querying every time.
+        List<String> carrierServicePackages =
+                getCarrierPackageNamesForIntentAndPhone(
+                        new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), logicalSlotIndex);
+        if (carrierServicePackages != null && !carrierServicePackages.isEmpty()) {
+            return carrierServicePackages.get(0);
+        }
+        return null;
+    }
+
     /** @hide */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public List<String> getPackagesWithCarrierPrivileges() {
@@ -10188,7 +10439,9 @@
      *
      * <p>Requires one of the following permissions:
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE},
-     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}, or that the calling app has carrier
+     * {@link android.Manifest.permission#MODIFY_PHONE_STATE}, or
+     * {@link android.Manifest.permission#READ_BASIC_PHONE_STATE
+     * READ_BASIC_PHONE_STATE} or that the calling app has carrier
      * privileges (see {@link #hasCarrierPrivileges}).
      *
      * <p>Note that this does not take into account any data restrictions that may be present on the
@@ -10199,7 +10452,8 @@
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.MODIFY_PHONE_STATE,
-            android.Manifest.permission.READ_PHONE_STATE})
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE})
     public boolean isDataEnabled() {
         try {
             return isDataEnabledForReason(DATA_ENABLED_REASON_USER);
@@ -10218,14 +10472,17 @@
      *
      * <p>Requires one of the following permissions:
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE},
-     * {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling app
+     * {@link android.Manifest.permission#READ_PHONE_STATE} or
+     * {@link android.Manifest.permission#READ_BASIC_PHONE_STATE
+     * READ_BASIC_PHONE_STATE} or that the calling app
      * has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @return {@code true} if the data roaming is enabled on the subscription, otherwise return
      * {@code false}.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
-            android.Manifest.permission.READ_PHONE_STATE})
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE})
     public boolean isDataRoamingEnabled() {
         boolean isDataRoamingEnabled = false;
         try {
@@ -11648,7 +11905,6 @@
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
      * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
-     *
      * May return {@code null} when the subscription is inactive or when there was an error
      * communicating with the phone process.
      */
@@ -11658,7 +11914,72 @@
             Manifest.permission.ACCESS_COARSE_LOCATION
     })
     public @Nullable ServiceState getServiceState() {
-        return getServiceStateForSubscriber(getSubId());
+        return getServiceState(false, false);
+    }
+
+    /**
+     * Returns the current {@link ServiceState} information.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     *
+     * If you want continuous updates of service state info, register a {@link PhoneStateListener}
+     * via {@link #listen} with the {@link PhoneStateListener#LISTEN_SERVICE_STATE} event.
+     *
+     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges})
+     * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+     * location related information which will be sent if the caller already possess
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
+     * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+     * receive location related information which will be sent if the caller already possess
+     * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+     * May return {@code null} when the subscription is inactive or when there was an error
+     * communicating with the phone process.
+     */
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+    @RequiresPermission(allOf = {
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.ACCESS_COARSE_LOCATION
+    })
+    public @Nullable ServiceState getServiceState(boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess) {
+        return getServiceStateForSubscriber(getSubId(), renounceFineLocationAccess,
+                renounceCoarseLocationAccess);
+    }
+
+    /**
+     * Returns the service state information on specified subscription. Callers require
+     * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information.
+     *
+     * May return {@code null} when the subscription is inactive or when there was an error
+     * communicating with the phone process.
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+     * location related information which will be sent if the caller already possess
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission
+     * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+     * receive location related information which will be sent if the caller already possess
+     * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+     */
+    private ServiceState getServiceStateForSubscriber(int subId,
+            boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess) {
+        try {
+            ITelephony service = getITelephony();
+            if (service != null) {
+                return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess,
+                        renounceCoarseLocationAccess,
+                        getOpPackageName(), getAttributionTag());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
+        } catch (NullPointerException e) {
+            AnomalyReporter.reportAnomaly(
+                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+                    "getServiceStateForSubscriber " + subId + " NPE");
+        }
+        return null;
     }
 
     /**
@@ -11671,20 +11992,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public ServiceState getServiceStateForSubscriber(int subId) {
-        try {
-            ITelephony service = getITelephony();
-            if (service != null) {
-                return service.getServiceStateForSubscriber(subId, getOpPackageName(),
-                        getAttributionTag());
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
-        } catch (NullPointerException e) {
-            AnomalyReporter.reportAnomaly(
-                    UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
-                    "getServiceStateForSubscriber " + subId + " NPE");
-        }
-        return null;
+        return getServiceStateForSubscriber(getSubId(), false, false);
     }
 
     /**
@@ -12627,11 +12935,13 @@
      * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or
      * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} or
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE}
+     * {@link android.Manifest.permission#READ_BASIC_PHONE_STATE}
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.READ_PHONE_STATE,
-            android.Manifest.permission.MODIFY_PHONE_STATE
+            android.Manifest.permission.MODIFY_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE
     })
     public boolean isDataEnabledForReason(@DataEnabledReason int reason) {
         return isDataEnabledForReason(getSubId(), reason);
@@ -12767,14 +13077,11 @@
      *   <LI>And possibly others.</LI>
      * </UL>
      * @return {@code true} if the overall data connection is allowed; {@code false} if not.
-     * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or
-     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} or
-     * android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.READ_PHONE_STATE,
-            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_BASIC_PHONE_STATE})
     public boolean isDataConnectionAllowed() {
         boolean retVal = false;
         try {
@@ -15536,7 +15843,49 @@
      */
     public void registerTelephonyCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull TelephonyCallback callback) {
+        registerTelephonyCallback(false, false, executor, callback);
+    }
 
+    /**
+     * Registers a callback object to receive notification of changes in specified telephony states.
+     * <p>
+     * To register a callback, pass a {@link TelephonyCallback} which implements
+     * interfaces of events. For example,
+     * FakeServiceStateCallback extends {@link TelephonyCallback} implements
+     * {@link TelephonyCallback.ServiceStateListener}.
+     *
+     * At registration, and when a specified telephony state changes, the telephony manager invokes
+     * the appropriate callback method on the callback object and passes the current (updated)
+     * values.
+     * <p>
+     *
+     * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
+     * applies to the given subId. Otherwise, applies to
+     * {@link SubscriptionManager#getDefaultSubscriptionId()}. To register events for multiple
+     * subIds, pass a separate callback object to each TelephonyManager object created with
+     * {@link #createForSubscriptionId}.
+     *
+     * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
+     * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
+     * {@link SecurityException} will be thrown otherwise.
+     *
+     * This API should be used sparingly -- large numbers of callbacks will cause system
+     * instability. If a process has registered too many callbacks without unregistering them, it
+     * may encounter an {@link IllegalStateException} when trying to register more callbacks.
+     *
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+     * location related information which will be sent if the caller already possess
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permissions.
+     * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+     * receive location related information which will be sent if the caller already possess
+     * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions.
+     * @param executor The executor of where the callback will execute.
+     * @param callback The {@link TelephonyCallback} object to register.
+     */
+    public void registerTelephonyCallback(boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull TelephonyCallback callback) {
         if (mContext == null) {
             throw new IllegalStateException("telephony service is null.");
         }
@@ -15547,7 +15896,8 @@
         mTelephonyRegistryMgr = (TelephonyRegistryManager)
                 mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
         if (mTelephonyRegistryMgr != null) {
-            mTelephonyRegistryMgr.registerTelephonyCallback(executor, mSubId, getOpPackageName(),
+            mTelephonyRegistryMgr.registerTelephonyCallback(renounceFineLocationAccess,
+                    renounceCoarseLocationAccess, executor, mSubId, getOpPackageName(),
                     getAttributionTag(), callback, getITelephony() != null);
         } else {
             throw new IllegalStateException("telephony service is null.");
@@ -16071,4 +16421,103 @@
             ex.rethrowAsRuntimeException();
         }
     }
+
+    /**
+     * Get last known cell identity.
+     * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
+     * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
+     * If there is current registered network this value will be same as the registered cell
+     * identity. If the device goes out of service the previous cell identity is cached and
+     * will be returned. If the cache age of the Cell identity is more than 24 hours
+     * it will be cleared and null will be returned.
+     * @return last known cell identity {@CellIdentity}.
+     * @hide
+     */
+    @RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
+            "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
+    public @Nullable CellIdentity getLastKnownCellIdentity() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony == null) {
+                throw new IllegalStateException("telephony service is null.");
+            }
+            return telephony.getLastKnownCellIdentity(getSubId(), getOpPackageName(),
+                    getAttributionTag());
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return null;
+    }
+
+    /**
+     * Callback to listen for when the set of packages with carrier privileges for a SIM changes.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface CarrierPrivilegesListener {
+        /**
+         * Called when the set of packages with carrier privileges has changed.
+         *
+         * <p>Of note, this callback will <b>not</b> be fired if a carrier triggers a SIM profile
+         * switch and the same set of packages remains privileged after the switch.
+         *
+         * <p>At registration, the callback will receive the current set of privileged packages.
+         *
+         * @param privilegedPackageNames The updated set of package names that have carrier
+         *     privileges
+         * @param privilegedUids The updated set of UIDs that have carrier privileges
+         */
+        void onCarrierPrivilegesChanged(
+                @NonNull List<String> privilegedPackageNames, @NonNull int[] privilegedUids);
+    }
+
+    /**
+     * Registers a {@link CarrierPrivilegesListener} on the given {@code logicalSlotIndex} to
+     * receive callbacks when the set of packages with carrier privileges changes. The callback will
+     * immediately be called with the latest state.
+     *
+     * @param logicalSlotIndex The SIM slot to listen on
+     * @param executor The executor where {@code listener} will be invoked
+     * @param listener The callback to register
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void addCarrierPrivilegesListener(
+            int logicalSlotIndex,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull CarrierPrivilegesListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        } else if (executor == null || listener == null) {
+            throw new IllegalArgumentException(
+                    "CarrierPrivilegesListener and executor must be non-null");
+        }
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.addCarrierPrivilegesListener(logicalSlotIndex, executor, listener);
+    }
+
+    /**
+     * Unregisters an existing {@link CarrierPrivilegesListener}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public void removeCarrierPrivilegesListener(@NonNull CarrierPrivilegesListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        } else if (listener == null) {
+            throw new IllegalArgumentException("CarrierPrivilegesListener must be non-null");
+        }
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.removeCarrierPrivilegesListener(listener);
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 9572154..122662d 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -228,7 +228,9 @@
      * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
-     *
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to receive
+     * location related information which will be sent if the caller already possess
+     * {@link android.Manifest.permission.ACCESS_FINE_LOCATION} and do not renounce the permission
      * @param request Contains all the RAT with bands/channels that need to be scanned.
      * @param callback Returns network scan results or errors.
      * @param callingPackage The package name of the caller
@@ -237,6 +239,7 @@
      * @hide
      */
     public NetworkScan requestNetworkScan(int subId,
+            boolean renounceFineLocationAccess,
             NetworkScanRequest request, Executor executor, NetworkScanCallback callback,
             String callingPackage, @Nullable String callingFeatureId) {
         try {
@@ -252,7 +255,8 @@
             // the record to the ScanInfo cache.
             synchronized (mScanInfo) {
                 int scanId = telephony.requestNetworkScan(
-                        subId, request, mMessenger, new Binder(), callingPackage,
+                        subId, renounceFineLocationAccess, request, mMessenger,
+                        new Binder(), callingPackage,
                         callingFeatureId);
                 if (scanId == INVALID_SCAN_ID) {
                     Rlog.e(TAG, "Failed to initiate network scan");
diff --git a/telephony/java/android/telephony/ThermalMitigationRequest.java b/telephony/java/android/telephony/ThermalMitigationRequest.java
index 91ad9c3..a0676ea 100644
--- a/telephony/java/android/telephony/ThermalMitigationRequest.java
+++ b/telephony/java/android/telephony/ThermalMitigationRequest.java
@@ -100,7 +100,7 @@
 
     private ThermalMitigationRequest(Parcel in) {
         mThermalMitigationAction = in.readInt();
-        mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader());
+        mDataThrottlingRequest = in.readParcelable(DataThrottlingRequest.class.getClassLoader(), android.telephony.DataThrottlingRequest.class);
     }
 
      /**
diff --git a/telephony/java/android/telephony/VisualVoicemailSms.java b/telephony/java/android/telephony/VisualVoicemailSms.java
index 085f882..bec715e 100644
--- a/telephony/java/android/telephony/VisualVoicemailSms.java
+++ b/telephony/java/android/telephony/VisualVoicemailSms.java
@@ -121,7 +121,7 @@
                 @Override
                 public VisualVoicemailSms createFromParcel(Parcel in) {
                     return new Builder()
-                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null))
+                            .setPhoneAccountHandle((PhoneAccountHandle) in.readParcelable(null, android.telecom.PhoneAccountHandle.class))
                             .setPrefix(in.readString())
                             .setFields(in.readBundle())
                             .setMessageBody(in.readString())
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 8c02ffe..9ed230a 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1629,7 +1629,7 @@
                 .setApnName(in.readString())
                 .setProxyAddress(in.readString())
                 .setProxyPort(in.readInt())
-                .setMmsc(in.readParcelable(Uri.class.getClassLoader()))
+                .setMmsc(in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class))
                 .setMmsProxyAddress(in.readString())
                 .setMmsProxyPort(in.readInt())
                 .setUser(in.readString())
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index ef02589..ae0d4e7 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -241,24 +241,24 @@
         mProtocolType = source.readInt();
         mInterfaceName = source.readString();
         mAddresses = new ArrayList<>();
-        source.readList(mAddresses, LinkAddress.class.getClassLoader());
+        source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
         mDnsAddresses = new ArrayList<>();
-        source.readList(mDnsAddresses, InetAddress.class.getClassLoader());
+        source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
         mGatewayAddresses = new ArrayList<>();
-        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader());
+        source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
         mPcscfAddresses = new ArrayList<>();
-        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader());
+        source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class);
         mMtu = source.readInt();
         mMtuV4 = source.readInt();
         mMtuV6 = source.readInt();
         mHandoverFailureMode = source.readInt();
         mPduSessionId = source.readInt();
-        mDefaultQos = source.readParcelable(Qos.class.getClassLoader());
+        mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
         mQosBearerSessions = new ArrayList<>();
-        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader());
-        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader());
+        source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class);
+        mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class);
         mTrafficDescriptors = new ArrayList<>();
-        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader());
+        source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index c1d16a9..479f057 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -105,8 +105,8 @@
 
     private DataProfile(Parcel source) {
         mType = source.readInt();
-        mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader());
-        mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader());
+        mApnSetting = source.readParcelable(ApnSetting.class.getClassLoader(), android.telephony.data.ApnSetting.class);
+        mTrafficDescriptor = source.readParcelable(TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class);
         mPreferred = source.readBoolean();
         mSetupTimestamp = source.readLong();
     }
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 2f03475..892eb29 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -402,6 +402,21 @@
         }
 
         /**
+         * Notify the system that a given DataProfile was unthrottled.
+         *
+         * @param dataProfile DataProfile associated with an APN returned from the modem
+         */
+        public final void notifyDataProfileUnthrottled(@NonNull DataProfile dataProfile) {
+            synchronized (mApnUnthrottledCallbacks) {
+                for (IDataServiceCallback callback : mApnUnthrottledCallbacks) {
+                    mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED,
+                            mSlotIndex, 0, new ApnUnthrottledIndication(dataProfile,
+                                    callback)).sendToTarget();
+                }
+            }
+        }
+
+        /**
          * Called when the instance of data service is destroyed (e.g. got unbind or binder died)
          * or when the data service provider is removed. The extended class should implement this
          * method to perform cleanup works.
@@ -496,13 +511,20 @@
     }
 
     private static final class ApnUnthrottledIndication {
+        public final DataProfile dataProfile;
         public final String apn;
         public final IDataServiceCallback callback;
         ApnUnthrottledIndication(String apn,
                 IDataServiceCallback callback) {
+            this.dataProfile = null;
             this.apn = apn;
             this.callback = callback;
         }
+        ApnUnthrottledIndication(DataProfile dataProfile, IDataServiceCallback callback) {
+            this.dataProfile = dataProfile;
+            this.apn = null;
+            this.callback = callback;
+        }
     }
 
     private class DataServiceHandler extends Handler {
@@ -636,8 +658,13 @@
                     ApnUnthrottledIndication apnUnthrottledIndication =
                             (ApnUnthrottledIndication) message.obj;
                     try {
-                        apnUnthrottledIndication.callback
-                                .onApnUnthrottled(apnUnthrottledIndication.apn);
+                        if (apnUnthrottledIndication.dataProfile != null) {
+                            apnUnthrottledIndication.callback
+                                    .onDataProfileUnthrottled(apnUnthrottledIndication.dataProfile);
+                        } else {
+                            apnUnthrottledIndication.callback
+                                    .onApnUnthrottled(apnUnthrottledIndication.apn);
+                        }
                     } catch (RemoteException e) {
                         loge("Failed to call onApnUnthrottled. " + e);
                     }
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index d082715..051d6c5 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -260,8 +260,8 @@
 
     /**
      * Unthrottles the APN on the current transport.  There is no matching "APN throttle" method.
-     * Instead, the APN is throttled for the time specified in
-     * {@link DataCallResponse#getRetryDurationMillis}.
+     * Instead, the APN is throttled when {@link IDataService#setupDataCall} fails within
+     * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
      * <p/>
      * see: {@link DataCallResponse#getRetryDurationMillis}
      *
@@ -279,4 +279,27 @@
             Rlog.e(TAG, "onApnUnthrottled: callback is null!");
         }
     }
+
+    /**
+     * Unthrottles the DataProfile on the current transport.
+     * There is no matching "DataProfile throttle" method.
+     * Instead, the DataProfile is throttled when {@link IDataService#setupDataCall} fails within
+     * the time specified by {@link DataCallResponse#getRetryDurationMillis}.
+     * <p/>
+     * see: {@link DataCallResponse#getRetryDurationMillis}
+     *
+     * @param dataProfile DataProfile containing the APN to be throttled
+     */
+    public void onDataProfileUnthrottled(final @NonNull DataProfile dataProfile) {
+        if (mCallback != null) {
+            try {
+                if (DBG) Rlog.d(TAG, "onDataProfileUnthrottled");
+                mCallback.onDataProfileUnthrottled(dataProfile);
+            } catch (RemoteException e) {
+                Rlog.e(TAG, "onDataProfileUnthrottled: remote exception", e);
+            }
+        } else {
+            Rlog.e(TAG, "onDataProfileUnthrottled: callback is null!");
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
index 9cc2fea..8205b5e 100644
--- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl
+++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl
@@ -17,6 +17,7 @@
 package android.telephony.data;
 
 import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataProfile;
 
 /**
  * The call back interface
@@ -33,4 +34,5 @@
     void onHandoverStarted(int result);
     void onHandoverCancelled(int result);
     void onApnUnthrottled(in String apn);
+    void onDataProfileUnthrottled(in DataProfile dp);
 }
diff --git a/telephony/java/android/telephony/data/Qos.java b/telephony/java/android/telephony/data/Qos.java
index 8c437c8..9c2a3bb 100644
--- a/telephony/java/android/telephony/data/Qos.java
+++ b/telephony/java/android/telephony/data/Qos.java
@@ -136,8 +136,8 @@
 
     protected Qos(@NonNull Parcel source) {
         type = source.readInt();
-        downlink = source.readParcelable(QosBandwidth.class.getClassLoader());
-        uplink = source.readParcelable(QosBandwidth.class.getClassLoader());
+        downlink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
+        uplink = source.readParcelable(QosBandwidth.class.getClassLoader(), android.telephony.data.Qos.QosBandwidth.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/data/QosBearerFilter.java b/telephony/java/android/telephony/data/QosBearerFilter.java
index 5f5762b..5a7189f 100644
--- a/telephony/java/android/telephony/data/QosBearerFilter.java
+++ b/telephony/java/android/telephony/data/QosBearerFilter.java
@@ -257,11 +257,11 @@
 
     private QosBearerFilter(Parcel source) {
         localAddresses = new ArrayList<>();
-        source.readList(localAddresses, LinkAddress.class.getClassLoader());
+        source.readList(localAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
         remoteAddresses = new ArrayList<>();
-        source.readList(remoteAddresses, LinkAddress.class.getClassLoader());
-        localPort = source.readParcelable(PortRange.class.getClassLoader());
-        remotePort = source.readParcelable(PortRange.class.getClassLoader());
+        source.readList(remoteAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class);
+        localPort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class);
+        remotePort = source.readParcelable(PortRange.class.getClassLoader(), android.telephony.data.QosBearerFilter.PortRange.class);
         protocol = source.readInt();
         typeOfServiceMask = source.readInt();
         flowLabel = source.readLong();
diff --git a/telephony/java/android/telephony/data/QosBearerSession.java b/telephony/java/android/telephony/data/QosBearerSession.java
index ffeb08a..dd08085 100644
--- a/telephony/java/android/telephony/data/QosBearerSession.java
+++ b/telephony/java/android/telephony/data/QosBearerSession.java
@@ -46,9 +46,9 @@
 
     private QosBearerSession(Parcel source) {
         qosBearerSessionId = source.readInt();
-        qos = source.readParcelable(Qos.class.getClassLoader());
+        qos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class);
         qosBearerFilterList = new ArrayList<>();
-        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader());
+        source.readList(qosBearerFilterList, QosBearerFilter.class.getClassLoader(), android.telephony.data.QosBearerFilter.class);
     }
 
     public int getQosBearerSessionId() {
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index ab35d77..885244e 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -66,14 +66,15 @@
 
     /** Reason for canceling a profile download session */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "CANCEL_REASON_" }, value = {
+    @IntDef(prefix = {"CANCEL_REASON_"}, value = {
             CANCEL_REASON_END_USER_REJECTED,
             CANCEL_REASON_POSTPONED,
             CANCEL_REASON_TIMEOUT,
             CANCEL_REASON_PPR_NOT_ALLOWED
     })
     /** @hide */
-    public @interface CancelReason {}
+    public @interface CancelReason {
+    }
 
     /**
      * The end user has rejected the download. The profile will be put into the error state and
@@ -96,13 +97,14 @@
 
     /** Options for resetting eUICC memory */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(flag = true, prefix = { "RESET_OPTION_" }, value = {
+    @IntDef(flag = true, prefix = {"RESET_OPTION_"}, value = {
             RESET_OPTION_DELETE_OPERATIONAL_PROFILES,
             RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES,
             RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS
     })
     /** @hide */
-    public @interface ResetOption {}
+    public @interface ResetOption {
+    }
 
     /** Deletes all operational profiles. */
     public static final int RESET_OPTION_DELETE_OPERATIONAL_PROFILES = 1;
@@ -124,6 +126,10 @@
 
     /** 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 is not found */
+    public static final int RESULT_PROFILE_NOT_FOUND = -4;
+
     /**
      * Callback to receive the result of an eUICC card API.
      *
@@ -134,9 +140,9 @@
          * This method will be called when an eUICC card API call is completed.
          *
          * @param resultCode This can be {@link #RESULT_OK} or other positive values returned by the
-         *     eUICC.
-         * @param result The result object. It can be null if the {@code resultCode} is not
-         *     {@link #RESULT_OK}.
+         *                   eUICC.
+         * @param result     The result object. It can be null if the {@code resultCode} is not
+         *                   {@link #RESULT_OK}.
          */
         void onComplete(int resultCode, T result);
     }
@@ -159,7 +165,7 @@
     /**
      * Requests all the profiles on eUicc.
      *
-     * @param cardId The Id of the eUICC.
+     * @param cardId   The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and all the profiles.
      */
@@ -187,8 +193,8 @@
     /**
      * Requests the profile of the given iccid.
      *
-     * @param cardId The Id of the eUICC.
-     * @param iccid The iccid of the profile.
+     * @param cardId   The Id of the eUICC.
+     * @param iccid    The iccid of the profile.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and profile.
      */
@@ -214,16 +220,47 @@
     }
 
     /**
+     * Requests the enabled profile for a given port on an eUicc.
+     *
+     * @param cardId    The Id of the eUICC.
+     * @param portIndex The portIndex to use. The port may be active or inactive. As long as the
+     *                  ICCID is known, an APDU will be sent through to read the enabled profile.
+     * @param executor  The executor through which the callback should be invoked.
+     * @param callback  The callback to get the result code and the profile.
+     */
+    public void requestEnabledProfileForPort(@NonNull String cardId, int portIndex,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ResultCallback<EuiccProfileInfo> callback) {
+        try {
+            getIEuiccCardController().getEnabledProfile(mContext.getOpPackageName(), cardId,
+                    portIndex,
+                    new IGetProfileCallback.Stub() {
+                        @Override
+                        public void onComplete(int resultCode, EuiccProfileInfo profile) {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                executor.execute(() -> callback.onComplete(resultCode, profile));
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                        }
+                    });
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error calling requestEnabledProfileForPort", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Disables the profile of the given iccid.
      *
-     * @param cardId The Id of the eUICC.
-     * @param iccid The iccid of the profile.
-     * @param refresh Whether sending the REFRESH command to modem.
+     * @param cardId   The Id of the eUICC.
+     * @param iccid    The iccid of the profile.
+     * @param refresh  Whether sending the REFRESH command to modem.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code.
-     *
      * @deprecated instead use {@link #disableProfile(String, String, int, boolean, Executor,
-     *             ResultCallback)}
+     * ResultCallback)}
      */
     @Deprecated
     public void disableProfile(String cardId, String iccid, boolean refresh,
@@ -247,15 +284,16 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
     /**
      * Disables the profile of the given ICCID.
      *
-     * @param cardId The Id of the eUICC.
-     * @param iccid The iccid of the profile.
+     * @param cardId    The Id of the eUICC.
+     * @param iccid     The iccid of the profile.
      * @param portIndex the Port index is the unique index referring to a port.
-     * @param refresh Whether sending the REFRESH command to modem.
-     * @param executor The executor through which the callback should be invoked.
-     * @param callback The callback to get the result code.
+     * @param refresh   Whether sending the REFRESH command to modem.
+     * @param executor  The executor through which the callback should be invoked.
+     * @param callback  The callback to get the result code.
      */
     public void disableProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
             boolean refresh, @NonNull @CallbackExecutor Executor executor,
@@ -283,14 +321,13 @@
      * Switches from the current profile to another profile. The current profile will be disabled
      * and the specified profile will be enabled.
      *
-     * @param cardId The Id of the eUICC.
-     * @param iccid The iccid of the profile to switch to.
-     * @param refresh Whether sending the REFRESH command to modem.
+     * @param cardId   The Id of the eUICC.
+     * @param iccid    The iccid of the profile to switch to.
+     * @param refresh  Whether sending the REFRESH command to modem.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and the EuiccProfileInfo enabled.
-     *
      * @deprecated instead use {@link #switchToProfile(String, String, int, boolean, Executor,
-     *             ResultCallback)}
+     * ResultCallback)}
      */
     @Deprecated
     public void switchToProfile(String cardId, String iccid, boolean refresh,
@@ -320,12 +357,12 @@
      * and the specified profile will be enabled. Here portIndex specifies on which port the
      * profile is to be enabled.
      *
-     * @param cardId The Id of the eUICC.
-     * @param iccid The iccid of the profile to switch to.
+     * @param cardId    The Id of the eUICC.
+     * @param iccid     The iccid of the profile to switch to.
      * @param portIndex The Port index is the unique index referring to a port.
-     * @param refresh Whether sending the REFRESH command to modem.
-     * @param executor The executor through which the callback should be invoked.
-     * @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+     * @param refresh   Whether sending the REFRESH command to modem.
+     * @param executor  The executor through which the callback should be invoked.
+     * @param callback  The callback to get the result code and the EuiccProfileInfo enabled.
      */
     public void switchToProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
             boolean refresh, @NonNull @CallbackExecutor Executor executor,
diff --git a/telephony/java/android/telephony/gba/GbaAuthRequest.java b/telephony/java/android/telephony/gba/GbaAuthRequest.java
index 5366e9a..2c6021a 100644
--- a/telephony/java/android/telephony/gba/GbaAuthRequest.java
+++ b/telephony/java/android/telephony/gba/GbaAuthRequest.java
@@ -120,7 +120,7 @@
                     int token = in.readInt();
                     int subId = in.readInt();
                     int appType = in.readInt();
-                    Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader());
+                    Uri nafUrl = in.readParcelable(GbaAuthRequest.class.getClassLoader(), android.net.Uri.class);
                     int len = in.readInt();
                     byte[] protocol = new byte[len];
                     in.readByteArray(protocol);
diff --git a/telephony/java/android/telephony/ims/DelegateRegistrationState.java b/telephony/java/android/telephony/ims/DelegateRegistrationState.java
index 1b10404..c2c9497 100644
--- a/telephony/java/android/telephony/ims/DelegateRegistrationState.java
+++ b/telephony/java/android/telephony/ims/DelegateRegistrationState.java
@@ -97,7 +97,24 @@
      */
     public static final int DEREGISTERING_REASON_DESTROY_PENDING = 6;
 
-    /** @hide */
+    /**
+     * This feature tag is deregistering because the PDN that the IMS registration is on
+     * is being torn down.
+     * <p>
+     * All open SIP Dialogs associated with this feature tag must be  closed
+     * using {@link SipDelegateConnection#cleanupSession(String)} before this operation can proceed.
+     */
+    public static final int DEREGISTERING_REASON_LOSING_PDN = 7;
+
+    /**
+     * This feature tag is deregistering because of an unspecified reason.
+     * <p>
+     * All open SIP Dialogs associated with this feature tag must be  closed
+     * using {@link SipDelegateConnection#cleanupSession(String)} before this operation can proceed.
+     */
+    public static final int DEREGISTERING_REASON_UNSPECIFIED = 8;
+
+/** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "DEREGISTERED_REASON_", value = {
             DEREGISTERED_REASON_UNKNOWN,
@@ -113,7 +130,9 @@
             DEREGISTERING_REASON_PDN_CHANGE,
             DEREGISTERING_REASON_PROVISIONING_CHANGE,
             DEREGISTERING_REASON_FEATURE_TAGS_CHANGING,
-            DEREGISTERING_REASON_DESTROY_PENDING
+            DEREGISTERING_REASON_DESTROY_PENDING,
+            DEREGISTERING_REASON_LOSING_PDN,
+            DEREGISTERING_REASON_UNSPECIFIED
     })
     public @interface DeregisteringReason {}
 
diff --git a/telephony/java/android/telephony/ims/DelegateRequest.java b/telephony/java/android/telephony/ims/DelegateRequest.java
index c322d92..c5c9200 100644
--- a/telephony/java/android/telephony/ims/DelegateRequest.java
+++ b/telephony/java/android/telephony/ims/DelegateRequest.java
@@ -63,7 +63,7 @@
      */
     private DelegateRequest(Parcel in) {
         mFeatureTags = new ArrayList<>();
-        in.readList(mFeatureTags, null /*classLoader*/);
+        in.readList(mFeatureTags, null /*classLoader*/, java.lang.String.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 486f746..93e1058 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -839,7 +839,7 @@
         mServiceType = in.readInt();
         mCallType = in.readInt();
         mCallExtras = in.readBundle();
-        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader());
+        mMediaProfile = in.readParcelable(ImsStreamMediaProfile.class.getClassLoader(), android.telephony.ims.ImsStreamMediaProfile.class);
         mEmergencyServiceCategories = in.readInt();
         mEmergencyUrns = in.createStringArrayList();
         mEmergencyCallRouting = in.readInt();
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
index dfe5e6c9..6569de6 100755
--- a/telephony/java/android/telephony/ims/ImsCallSession.java
+++ b/telephony/java/android/telephony/ims/ImsCallSession.java
@@ -27,10 +27,12 @@
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * Provides the call initiation/termination, and media exchange between two IMS endpoints.
@@ -522,6 +524,7 @@
     private final IImsCallSession miSession;
     private boolean mClosed = false;
     private Listener mListener;
+    private Executor mListenerExecutor = Runnable::run;
 
     /** @hide */
     public ImsCallSession(IImsCallSession iSession) {
@@ -538,9 +541,9 @@
     }
 
     /** @hide */
-    public ImsCallSession(IImsCallSession iSession, Listener listener) {
+    public ImsCallSession(IImsCallSession iSession, Listener listener, Executor executor) {
         this(iSession);
-        setListener(listener);
+        setListener(listener, executor);
     }
 
     /**
@@ -738,10 +741,14 @@
      * override the previous listener.
      *
      * @param listener to listen to the session events of this object
+     * @param executor an Executor that will execute callbacks
      * @hide
      */
-    public void setListener(Listener listener) {
+    public void setListener(Listener listener, Executor executor) {
         mListener = listener;
+        if (executor != null) {
+            mListenerExecutor = executor;
+        }
     }
 
     /**
@@ -1206,42 +1213,48 @@
         @Override
         public void callSessionInitiating(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionInitiating(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionInitiating(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionProgressing(ImsStreamMediaProfile profile) {
             if (mListener != null) {
-                mListener.callSessionProgressing(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionProgressing(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiated(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionStarted(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStarted(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiatingFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionStartFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTerminated(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionTerminated(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionTerminated(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1251,42 +1264,49 @@
         @Override
         public void callSessionHeld(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionHeld(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHeld(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionHoldFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionHoldReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionHoldReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionHoldReceived(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumed(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionResumed(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumed(
+                        ImsCallSession.this, profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumeFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionResumeFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionResumeReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionResumeReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionResumeReceived(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
@@ -1311,13 +1331,15 @@
         @Override
         public void callSessionMergeComplete(IImsCallSession newSession) {
             if (mListener != null) {
-                if (newSession != null) {
-                    // New session created after conference
-                    mListener.callSessionMergeComplete(new ImsCallSession(newSession));
-               } else {
-                   // Session already exists. Hence no need to pass
-                   mListener.callSessionMergeComplete(null);
-               }
+                TelephonyUtils.runWithCleanCallingIdentity(()-> {
+                    if (newSession != null) {
+                        // New session created after conference
+                        mListener.callSessionMergeComplete(new ImsCallSession(newSession));
+                    } else {
+                        // Session already exists. Hence no need to pass
+                        mListener.callSessionMergeComplete(null);
+                    }
+                }, mListenerExecutor);
             }
         }
 
@@ -1329,7 +1351,9 @@
         @Override
         public void callSessionMergeFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
@@ -1339,21 +1363,27 @@
         @Override
         public void callSessionUpdated(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionUpdated(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdated(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionUpdateReceived(ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionUpdateReceived(ImsCallSession.this, profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUpdateReceived(ImsCallSession.this, profile),
+                        mListenerExecutor);
             }
         }
 
@@ -1364,15 +1394,18 @@
         public void callSessionConferenceExtended(IImsCallSession newSession,
                 ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtended(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtended(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtendFailed(
+                        ImsCallSession.this, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1380,8 +1413,9 @@
         public void callSessionConferenceExtendReceived(IImsCallSession newSession,
                 ImsCallProfile profile) {
             if (mListener != null) {
-                mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
-                        new ImsCallSession(newSession), profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceExtendReceived(ImsCallSession.this,
+                        new ImsCallSession(newSession), profile), mListenerExecutor);
             }
         }
 
@@ -1392,30 +1426,36 @@
         @Override
         public void callSessionInviteParticipantsRequestDelivered() {
             if (mListener != null) {
-                mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionInviteParticipantsRequestDelivered(
+                        ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestDelivered() {
             if (mListener != null) {
-                mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRemoveParticipantsRequestDelivered(
+                        ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
-                        reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this,
+                        reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1425,7 +1465,9 @@
         @Override
         public void callSessionConferenceStateUpdated(ImsConferenceState state) {
             if (mListener != null) {
-                mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state),
+                        mListenerExecutor);
             }
         }
 
@@ -1435,7 +1477,9 @@
         @Override
         public void callSessionUssdMessageReceived(int mode, String ussdMessage) {
             if (mListener != null) {
-                mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode,
+                        ussdMessage), mListenerExecutor);
             }
         }
 
@@ -1453,8 +1497,9 @@
         @Override
         public void callSessionMayHandover(int srcNetworkType, int targetNetworkType) {
             if (mListener != null) {
-                mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMayHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType), mListenerExecutor);
             }
         }
 
@@ -1465,8 +1510,9 @@
         public void callSessionHandover(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionHandover(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1477,8 +1523,9 @@
         public void callSessionHandoverFailed(int srcNetworkType, int targetNetworkType,
                 ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
-                        targetNetworkType, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionHandoverFailed(ImsCallSession.this, srcNetworkType,
+                        targetNetworkType, reasonInfo), mListenerExecutor);
             }
         }
 
@@ -1488,7 +1535,9 @@
         @Override
         public void callSessionTtyModeReceived(int mode) {
             if (mListener != null) {
-                mListener.callSessionTtyModeReceived(ImsCallSession.this, mode);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTtyModeReceived(ImsCallSession.this, mode),
+                        mListenerExecutor);
             }
         }
 
@@ -1500,14 +1549,18 @@
          */
         public void callSessionMultipartyStateChanged(boolean isMultiParty) {
             if (mListener != null) {
-                mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionMultipartyStateChanged(ImsCallSession.this,
+                        isMultiParty), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) {
             if (mListener != null) {
-                mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionSuppServiceReceived(ImsCallSession.this,
+                        suppServiceInfo), mListenerExecutor);
             }
         }
 
@@ -1517,7 +1570,9 @@
         @Override
         public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) {
             if (mListener != null) {
-                mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttModifyRequestReceived(ImsCallSession.this,
+                        callProfile), mListenerExecutor);
             }
         }
 
@@ -1527,7 +1582,9 @@
         @Override
         public void callSessionRttModifyResponseReceived(int status) {
             if (mListener != null) {
-                mListener.callSessionRttModifyResponseReceived(status);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttModifyResponseReceived(status),
+                        mListenerExecutor);
             }
         }
 
@@ -1537,7 +1594,8 @@
         @Override
         public void callSessionRttMessageReceived(String rttMessage) {
             if (mListener != null) {
-                mListener.callSessionRttMessageReceived(rttMessage);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttMessageReceived(rttMessage), mListenerExecutor);
             }
         }
 
@@ -1547,21 +1605,26 @@
         @Override
         public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
             if (mListener != null) {
-                mListener.callSessionRttAudioIndicatorChanged(profile);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRttAudioIndicatorChanged(profile),
+                        mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTransferred() {
             if (mListener != null) {
-                mListener.callSessionTransferred(ImsCallSession.this);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTransferred(ImsCallSession.this), mListenerExecutor);
             }
         }
 
         @Override
         public void callSessionTransferFailed(@Nullable ImsReasonInfo reasonInfo) {
             if (mListener != null) {
-                mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo);
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionTransferFailed(ImsCallSession.this, reasonInfo),
+                        mListenerExecutor);
             }
         }
 
@@ -1572,7 +1635,8 @@
         @Override
         public void callSessionDtmfReceived(char dtmf) {
             if (mListener != null) {
-                mListener.callSessionDtmfReceived(dtmf);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callSessionDtmfReceived(
+                        dtmf), mListenerExecutor);
             }
         }
 
@@ -1582,7 +1646,8 @@
         @Override
         public void callQualityChanged(CallQuality callQuality) {
             if (mListener != null) {
-                mListener.callQualityChanged(callQuality);
+                TelephonyUtils.runWithCleanCallingIdentity(()-> mListener.callQualityChanged(
+                        callQuality), mListenerExecutor);
             }
         }
 
@@ -1594,8 +1659,9 @@
         public void callSessionRtpHeaderExtensionsReceived(
                 @NonNull List<RtpHeaderExtension> extensions) {
             if (mListener != null) {
-                mListener.callSessionRtpHeaderExtensionsReceived(
-                        new ArraySet<RtpHeaderExtension>(extensions));
+                TelephonyUtils.runWithCleanCallingIdentity(()->
+                        mListener.callSessionRtpHeaderExtensionsReceived(
+                        new ArraySet<RtpHeaderExtension>(extensions)), mListenerExecutor);
             }
         }
     }
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 1fa5f52..d4d8c44 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -133,7 +133,7 @@
 
         for (int i = 0; i < size; ++i) {
             String user = in.readString();
-            Bundle state = in.readParcelable(null);
+            Bundle state = in.readParcelable(null, android.os.Bundle.class);
             mParticipants.put(user, state);
         }
     }
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index c663e39..d451107 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -141,8 +141,8 @@
     public ImsExternalCallState(Parcel in) {
         mCallId = in.readInt();
         ClassLoader classLoader = ImsExternalCallState.class.getClassLoader();
-        mAddress = in.readParcelable(classLoader);
-        mLocalAddress = in.readParcelable(classLoader);
+        mAddress = in.readParcelable(classLoader, android.net.Uri.class);
+        mLocalAddress = in.readParcelable(classLoader, android.net.Uri.class);
         mIsPullable = (in.readInt() != 0);
         mCallState = in.readInt();
         mCallType = in.readInt();
diff --git a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
index ccb3231..b77d306 100644
--- a/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
+++ b/telephony/java/android/telephony/ims/ImsRegistrationAttributes.java
@@ -153,7 +153,7 @@
         mTransportType = source.readInt();
         mImsAttributeFlags = source.readInt();
         mFeatureTags = new ArrayList<>();
-        source.readList(mFeatureTags, null /*classloader*/);
+        source.readList(mFeatureTags, null /*classloader*/, java.lang.String.class);
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 9ab5aeb..53dff54 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -17,6 +17,7 @@
 package android.telephony.ims;
 
 import android.annotation.LongDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -44,11 +45,18 @@
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
 
 /**
  * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
@@ -173,7 +181,21 @@
     private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>();
 
     private IImsServiceControllerListener mListener;
+    private Executor mExecutor;
 
+    /**
+     * Create a new ImsService.
+     * <p>
+     * Method stubs called from the framework will be called asynchronously. Vendor specifies the
+     * {@link Executor} that the methods stubs will be called. If mExecutor is set to null by
+     * vendor use Runnable::run.
+     */
+    public ImsService() {
+        mExecutor = ImsService.this.getExecutor();
+        if (mExecutor == null) {
+            mExecutor = Runnable::run;
+        }
+    }
 
     /**
      * Listener that notifies the framework of ImsService changes.
@@ -201,78 +223,132 @@
 
         @Override
         public IImsMmTelFeature createMmTelFeature(int slotId) {
-            return createMmTelFeatureInternal(slotId);
+            return executeMethodAsyncForResult(() -> createMmTelFeatureInternal(slotId),
+                "createMmTelFeature");
         }
 
         @Override
         public IImsRcsFeature createRcsFeature(int slotId) {
-            return createRcsFeatureInternal(slotId);
+            return executeMethodAsyncForResult(() -> createRcsFeatureInternal(slotId),
+                "createRcsFeature");
         }
 
         @Override
         public void addFeatureStatusCallback(int slotId, int featureType,
                 IImsFeatureStatusCallback c) {
-            ImsService.this.addImsFeatureStatusCallback(slotId, featureType, c);
+            executeMethodAsync(() -> ImsService.this.addImsFeatureStatusCallback(
+                    slotId, featureType, c), "addFeatureStatusCallback");
         }
 
         @Override
         public void removeFeatureStatusCallback(int slotId, int featureType,
                 IImsFeatureStatusCallback c) {
-            ImsService.this.removeImsFeatureStatusCallback(slotId, featureType, c);
+            executeMethodAsync(() -> ImsService.this.removeImsFeatureStatusCallback(
+                    slotId, featureType, c), "removeFeatureStatusCallback");
         }
 
         @Override
         public void removeImsFeature(int slotId, int featureType) {
-            ImsService.this.removeImsFeature(slotId, featureType);
+            executeMethodAsync(() -> ImsService.this.removeImsFeature(slotId, featureType),
+                    "removeImsFeature");
         }
 
         @Override
         public ImsFeatureConfiguration querySupportedImsFeatures() {
-            return ImsService.this.querySupportedImsFeatures();
+            return executeMethodAsyncForResult(() -> ImsService.this.querySupportedImsFeatures(),
+                    "ImsFeatureConfiguration");
         }
 
         @Override
         public long getImsServiceCapabilities() {
-            long caps = ImsService.this.getImsServiceCapabilities();
-            long sanitizedCaps = sanitizeCapabilities(caps);
-            if (caps != sanitizedCaps) {
-                Log.w(LOG_TAG, "removing invalid bits from field: 0x"
-                        + Long.toHexString(caps ^ sanitizedCaps));
-            }
-            return sanitizedCaps;
+            return executeMethodAsyncForResult(() -> {
+                long caps = ImsService.this.getImsServiceCapabilities();
+                long sanitizedCaps = sanitizeCapabilities(caps);
+                if (caps != sanitizedCaps) {
+                    Log.w(LOG_TAG, "removing invalid bits from field: 0x"
+                            + Long.toHexString(caps ^ sanitizedCaps));
+                }
+                return sanitizedCaps;
+            }, "getImsServiceCapabilities");
         }
 
         @Override
         public void notifyImsServiceReadyForFeatureCreation() {
-            ImsService.this.readyForFeatureCreation();
+            executeMethodAsync(() -> ImsService.this.readyForFeatureCreation(),
+                    "notifyImsServiceReadyForFeatureCreation");
         }
 
         @Override
         public IImsConfig getConfig(int slotId) {
-            ImsConfigImplBase c = ImsService.this.getConfig(slotId);
-            return c != null ? c.getIImsConfig() : null;
+            return executeMethodAsyncForResult(() -> {
+                ImsConfigImplBase c = ImsService.this.getConfig(slotId);
+                if (c != null) {
+                    c.setDefaultExecutor(mExecutor);
+                    return c.getIImsConfig();
+                } else {
+                    return null;
+                }
+            }, "getConfig");
         }
 
         @Override
         public IImsRegistration getRegistration(int slotId) {
-            ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId);
-            return r != null ? r.getBinder() : null;
+            return executeMethodAsyncForResult(() -> {
+                ImsRegistrationImplBase r =  ImsService.this.getRegistration(slotId);
+                if (r != null) {
+                    r.setDefaultExecutor(mExecutor);
+                    return r.getBinder();
+                } else {
+                    return null;
+                }
+            }, "getRegistration");
         }
 
         @Override
         public ISipTransport getSipTransport(int slotId) {
-            SipTransportImplBase s = ImsService.this.getSipTransport(slotId);
-            return s != null ? s.getBinder() : null;
+            return executeMethodAsyncForResult(() -> {
+                SipTransportImplBase s =  ImsService.this.getSipTransport(slotId);
+                if (s != null) {
+                    s.setDefaultExecutor(mExecutor);
+                    return s.getBinder();
+                } else {
+                    return null;
+                }
+            }, "getSipTransport");
         }
 
         @Override
         public void enableIms(int slotId) {
-            ImsService.this.enableIms(slotId);
+            executeMethodAsync(() -> ImsService.this.enableIms(slotId), "enableIms");
         }
 
         @Override
         public void disableIms(int slotId) {
-            ImsService.this.disableIms(slotId);
+            executeMethodAsync(() -> ImsService.this.disableIms(slotId), "disableIms");
+        }
+
+        // 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) {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "ImsService Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(Supplier<T> r, String errorLogName) {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "ImsService Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                return null;
+            }
         }
     };
 
@@ -300,6 +376,7 @@
         MmTelFeature f = createMmTelFeature(slotId);
         if (f != null) {
             setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
+            f.setDefaultExecutor(mExecutor);
             return f.getBinder();
         } else {
             Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
@@ -310,6 +387,7 @@
     private IImsRcsFeature createRcsFeatureInternal(int slotId) {
         RcsFeature f = createRcsFeature(slotId);
         if (f != null) {
+            f.setDefaultExecutor(mExecutor);
             setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
             return f.getBinder();
         } else {
@@ -562,4 +640,15 @@
         result.append("}");
         return result.toString();
     }
+
+    /**
+     * The ImsService will now be able to define an Executor that the ImsService can be used to
+     * execute the methods. By default all ImsService level method calls will use this Executor.
+     * The ImsService has set the default executor as Runnable::run,
+     * Should be override or default executor will be used.
+     *  @return an Executor used to execute methods called remotely by the framework.
+     */
+    public @NonNull Executor getExecutor() {
+        return Runnable::run;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 868dea6a..9f4b77e 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -365,8 +365,8 @@
         serviceClass = in.readInt();
         result = in.readInt();
         mSsInfo = in.createIntArray();
-        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
-        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader());
+        mCfInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsCallForwardInfo.class);
+        mImsSsInfo = in.readParcelableList(new ArrayList<>(), this.getClass().getClassLoader(), android.telephony.ims.ImsSsInfo.class);
     }
 
     public static final @android.annotation.NonNull Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() {
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
index 793c377..c25ace0 100644
--- a/telephony/java/android/telephony/ims/RcsClientConfiguration.java
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -50,6 +50,7 @@
     private String mRcsProfile;
     private String mClientVendor;
     private String mClientVersion;
+    private boolean mRcsEnabledByUser;
 
     /**
      * Create a RcsClientConfiguration object.
@@ -63,14 +64,41 @@
      * @param clientVersion Identifies the RCS client version. Refer to GSMA
      * RCC.07 "client_version" parameter.
      * Example:client_version=RCSAndrd-1.0
+     * @deprecated Use {@link #RcsClientConfiguration(String, String, String, String, boolean)}
+     * instead. Deprecated prototype assumes that the user setting controlling RCS is enabled.
      */
+    @Deprecated
     public RcsClientConfiguration(@NonNull String rcsVersion,
             @NonNull @StringRcsProfile String rcsProfile,
             @NonNull String clientVendor, @NonNull String clientVersion) {
+        this(rcsVersion, rcsProfile, clientVendor, clientVersion, true);
+    }
+
+    /**
+     * Create a RcsClientConfiguration object.
+     * Default messaging application must pass a valid configuration object
+     * @param rcsVersion The parameter identifies the RCS version supported
+     * by the client. Refer to GSMA RCC.07 "rcs_version" parameter.
+     * @param rcsProfile Identifies a fixed set of RCS services that are
+     * supported by the client. See {@link #RCS_PROFILE_1_0 } or
+     * {@link #RCS_PROFILE_2_3 }
+     * @param clientVendor Identifies the vendor providing the RCS client.
+     * @param clientVersion Identifies the RCS client version. Refer to GSMA
+     * RCC.07 "client_version" parameter.
+     * Example:client_version=RCSAndrd-1.0
+     * @param isRcsEnabledByUser The current user setting for whether or not the user has
+     * enabled or disabled RCS. Please refer to GSMA RCC.07 "rcs_state" parameter for how this
+     * can affect provisioning.
+     */
+    public RcsClientConfiguration(@NonNull String rcsVersion,
+            @NonNull @StringRcsProfile String rcsProfile,
+            @NonNull String clientVendor, @NonNull String clientVersion,
+            boolean isRcsEnabledByUser) {
         mRcsVersion = rcsVersion;
         mRcsProfile = rcsProfile;
         mClientVendor = clientVendor;
         mClientVersion = clientVersion;
+        mRcsEnabledByUser = isRcsEnabledByUser;
     }
 
     /**
@@ -102,6 +130,18 @@
     }
 
     /**
+     * The current user setting provided by the RCS messaging application that determines
+     * whether or not the user has enabled RCS.
+     * <p>
+     * See GSMA RCC.07 "rcs_state" parameter for more information about how this setting
+     * affects provisioning.
+     * @return true if RCS is enabled by the user, false if RCS is disabled by the user.
+     */
+    public boolean isRcsEnabledByUser() {
+        return mRcsEnabledByUser;
+    }
+
+    /**
      * {@link Parcelable#writeToParcel}
      */
     @Override
@@ -110,6 +150,7 @@
         out.writeString(mRcsProfile);
         out.writeString(mClientVendor);
         out.writeString(mClientVersion);
+        out.writeBoolean(mRcsEnabledByUser);
     }
 
     /**
@@ -124,8 +165,9 @@
                     String rcsProfile = in.readString();
                     String clientVendor = in.readString();
                     String clientVersion = in.readString();
+                    Boolean rcsEnabledByUser = in.readBoolean();
                     return new RcsClientConfiguration(rcsVersion, rcsProfile,
-                            clientVendor, clientVersion);
+                            clientVendor, clientVersion, rcsEnabledByUser);
                 }
 
                 @Override
@@ -152,11 +194,13 @@
 
         return mRcsVersion.equals(other.mRcsVersion) && mRcsProfile.equals(other.mRcsProfile)
                 && mClientVendor.equals(other.mClientVendor)
-                && mClientVersion.equals(other.mClientVersion);
+                && mClientVersion.equals(other.mClientVersion)
+                && (mRcsEnabledByUser == other.mRcsEnabledByUser);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion);
+        return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion,
+                mRcsEnabledByUser);
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index 9c28c36..6a6c306 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -439,13 +439,13 @@
     }
 
     private RcsContactPresenceTuple(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mTimestamp = convertStringFormatTimeToInstant(in.readString());
         mStatus = in.readString();
         mServiceId = in.readString();
         mServiceVersion = in.readString();
         mServiceDescription = in.readString();
-        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader());
+        mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.ServiceCapabilities.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
index ee02564..ea022de 100644
--- a/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
+++ b/telephony/java/android/telephony/ims/RcsContactTerminatedReason.java
@@ -37,7 +37,7 @@
     }
 
     private RcsContactTerminatedReason(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mReason = in.readString();
     }
 
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 9112118..0f1b369 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -244,14 +244,14 @@
     }
 
     private RcsContactUceCapability(Parcel in) {
-        mContactUri = in.readParcelable(Uri.class.getClassLoader());
+        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
         mCapabilityMechanism = in.readInt();
         mSourceType = in.readInt();
         mRequestResult = in.readInt();
         List<String> featureTagList = new ArrayList<>();
         in.readStringList(featureTagList);
         mFeatureTags.addAll(featureTagList);
-        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
+        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 7a1c092..61de3ac 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -28,13 +28,10 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.ims.aidl.IImsRcsController;
 import android.telephony.ims.aidl.IRcsUceControllerCallback;
 import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
-import android.telephony.ims.feature.RcsFeature;
 import android.util.Log;
 
 import java.lang.annotation.Retention;
@@ -337,6 +334,14 @@
     @SystemApi
     public static final int PUBLISH_STATE_OTHER_ERROR = 6;
 
+    /**
+     * The device is currently trying to publish its capabilities to the network.
+     * @hide
+     */
+    @SystemApi
+    public static final int PUBLISH_STATE_PUBLISHING = 7;
+
+
     /**@hide*/
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "PUBLISH_STATE_", value = {
@@ -345,7 +350,8 @@
             PUBLISH_STATE_VOICE_PROVISION_ERROR,
             PUBLISH_STATE_RCS_PROVISION_ERROR,
             PUBLISH_STATE_REQUEST_TIMEOUT,
-            PUBLISH_STATE_OTHER_ERROR
+            PUBLISH_STATE_OTHER_ERROR,
+            PUBLISH_STATE_PUBLISHING
     })
     public @interface PublishState {}
 
@@ -480,9 +486,12 @@
      * <p>
      * Be sure to check the availability of this feature using
      * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
-     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
-     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is enabled or else
-     * this operation will fail with {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
+     * {@link
+     * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+     * {@link
+     * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
+     * enabled or else this operation will fail with {@link #ERROR_NOT_AVAILABLE} or
+     * {@link #ERROR_NOT_ENABLED}.
      *
      * @param contactNumbers A list of numbers that the capabilities are being requested for.
      * @param executor The executor that will be used when the request is completed and the
@@ -573,8 +582,10 @@
      * <p>
      * Be sure to check the availability of this feature using
      * {@link ImsRcsManager#isAvailable(int, int)} and ensuring
-     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
-     * {@link RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
+     * {@link
+     * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE} or
+     * {@link
+     * android.telephony.ims.feature.RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE} is
      * enabled or else this operation will fail with
      * {@link #ERROR_NOT_AVAILABLE} or {@link #ERROR_NOT_ENABLED}.
      *
@@ -690,7 +701,8 @@
      * Registers a {@link OnPublishStateChangedListener} with the system, which will provide publish
      * state updates for the subscription specified in {@link ImsManager@getRcsManager(subid)}.
      * <p>
-     * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to subscription
+     * Use {@link android.telephony.SubscriptionManager.OnSubscriptionsChangedListener} to listen
+     * to subscription
      * changed events and call
      * {@link #removeOnPublishStateChangedListener(OnPublishStateChangedListener)} to clean up.
      * <p>
@@ -792,7 +804,8 @@
      * cache associated with those contacts as the local cache becomes stale.
      * <p>
      * This setting will only enable this feature if
-     * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled.
+     * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is
+     * also enabled.
      * <p>
      * Note: This setting does not affect whether or not the device publishes its service
      * capabilities if the subscription supports presence publication.
@@ -843,7 +856,8 @@
      * session.
      * <p>
      * This setting will only enable this feature if
-     * {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is also enabled.
+     * {@link android.telephony.CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} is
+     * also enabled.
      * <p>
      * Note: This setting does not affect whether or not the device publishes its service
      * capabilities if the subscription supports presence publication.
diff --git a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
index af4e2347..b9ffd24 100644
--- a/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
+++ b/telephony/java/android/telephony/ims/RtpHeaderExtensionType.java
@@ -63,7 +63,7 @@
 
     private RtpHeaderExtensionType(Parcel in) {
         mLocalIdentifier = in.readInt();
-        mUri = in.readParcelable(Uri.class.getClassLoader());
+        mUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
     }
 
     public static final @NonNull Creator<RtpHeaderExtensionType> CREATOR =
diff --git a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
index 1bf5cad..db0ae03 100644
--- a/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
+++ b/telephony/java/android/telephony/ims/SipDelegateConfiguration.java
@@ -573,7 +573,7 @@
         mPrivateUserIdentifier = source.readString();
         mHomeDomain = source.readString();
         mImei = source.readString();
-        mGruu = source.readParcelable(null);
+        mGruu = source.readParcelable(null, android.net.Uri.class);
         mSipAuthHeader = source.readString();
         mSipAuthNonce = source.readString();
         mServiceRouteHeader = source.readString();
diff --git a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
index c3d7325..c27fa4f 100644
--- a/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/CapabilityExchangeAidlWrapper.java
@@ -81,6 +81,26 @@
     }
 
     /**
+     * Receives the status of changes in the publishing connection from ims service
+     * and deliver this callback to the framework.
+     */
+    public void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase,
+            int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException {
+        ICapabilityExchangeEventListener listener = mListenerBinder;
+        if (listener == null) {
+            return;
+        }
+        try {
+            listener.onPublishUpdated(reasonCode, reasonPhrase,
+                    reasonHeaderCause, reasonHeaderText);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "onPublishUpdated exception: " + e);
+            throw new ImsException("Remote is not available",
+                    ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+        }
+    }
+
+    /**
      * Receives the callback of the remote capability request from the network and deliver this
      * request to the framework.
      */
diff --git a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
index 078ac91..c675bc3 100644
--- a/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/ICapabilityExchangeEventListener.aidl
@@ -31,6 +31,8 @@
 oneway interface ICapabilityExchangeEventListener {
     void onRequestPublishCapabilities(int publishTriggerType);
     void onUnpublish();
+    void onPublishUpdated(int reasonCode, String reasonPhrase, int reasonHeaderCause,
+            String reasonHeaderText);
     void onRemoteCapabilityRequest(in Uri contactUri,
             in List<String> remoteCapabilities, IOptionsRequestCallback cb);
 }
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 9a3f592..7fdf21b 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -40,16 +40,25 @@
 import android.telephony.ims.stub.ImsSmsImplBase;
 import android.telephony.ims.stub.ImsUtImplBase;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsEcbm;
 import com.android.ims.internal.IImsMultiEndpoint;
 import com.android.ims.internal.IImsUt;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
 
 /**
  * Base implementation for Voice and SMS (IR-92) and Video (IR-94) IMS support.
@@ -60,6 +69,7 @@
 public class MmTelFeature extends ImsFeature {
 
     private static final String LOG_TAG = "MmTelFeature";
+    private Executor mExecutor;
 
     /**
      * @hide
@@ -68,160 +78,261 @@
     public MmTelFeature() {
     }
 
+    /**
+     * Create a new MmTelFeature using the Executor specified for methods being called by the
+     * framework.
+     * @param executor The executor for the framework to use when executing the methods overridden
+     * by the implementation of MmTelFeature.
+     * @hide
+     */
+    @SystemApi
+    public MmTelFeature(@NonNull Executor executor) {
+        super();
+        mExecutor = executor;
+    }
+
     private final IImsMmTelFeature mImsMMTelBinder = new IImsMmTelFeature.Stub() {
 
         @Override
         public void setListener(IImsMmTelListener l) {
-            MmTelFeature.this.setListener(l);
+            executeMethodAsyncNoException(() -> MmTelFeature.this.setListener(l), "setListener");
         }
 
         @Override
         public int getFeatureState() throws RemoteException {
-            try {
-                return MmTelFeature.this.getFeatureState();
-            } catch (Exception e) {
-                throw new RemoteException(e.getMessage());
-            }
+            return executeMethodAsyncForResult(() -> MmTelFeature.this.getFeatureState(),
+                    "getFeatureState");
         }
 
-
         @Override
         public ImsCallProfile createCallProfile(int callSessionType, int callType)
                 throws RemoteException {
-            synchronized (mLock) {
-                try {
-                    return MmTelFeature.this.createCallProfile(callSessionType, callType);
-                } catch (Exception e) {
-                    throw new RemoteException(e.getMessage());
-                }
-            }
+            return executeMethodAsyncForResult(() -> MmTelFeature.this.createCallProfile(
+                    callSessionType, callType), "createCallProfile");
         }
 
         @Override
         public void changeOfferedRtpHeaderExtensionTypes(List<RtpHeaderExtensionType> types)
                 throws RemoteException {
-            synchronized (mLock) {
-                try {
-                    MmTelFeature.this.changeOfferedRtpHeaderExtensionTypes(new ArraySet<>(types));
-                } catch (Exception e) {
-                    throw new RemoteException(e.getMessage());
-                }
-            }
+            executeMethodAsync(() -> MmTelFeature.this.changeOfferedRtpHeaderExtensionTypes(
+                    new ArraySet<>(types)), "changeOfferedRtpHeaderExtensionTypes");
         }
 
         @Override
         public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException {
-            synchronized (mLock) {
-                return createCallSessionInterface(profile);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            IImsCallSession result = executeMethodAsyncForResult(() -> {
+                try {
+                    return createCallSessionInterface(profile);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                    return null;
+                }
+            }, "createCallSession");
+
+            if (exceptionRef.get() != null) {
+                throw exceptionRef.get();
             }
+
+            return result;
         }
 
         @Override
         public int shouldProcessCall(String[] numbers) {
-            synchronized (mLock) {
-                return MmTelFeature.this.shouldProcessCall(numbers);
+            Integer result = executeMethodAsyncForResultNoException(() ->
+                    MmTelFeature.this.shouldProcessCall(numbers), "shouldProcessCall");
+            if (result != null) {
+                return result.intValue();
+            } else {
+                return PROCESS_CALL_CSFB;
             }
         }
 
         @Override
         public IImsUt getUtInterface() throws RemoteException {
-            synchronized (mLock) {
-                return MmTelFeature.this.getUtInterface();
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            IImsUt result = executeMethodAsyncForResult(() -> {
+                try {
+                    return MmTelFeature.this.getUtInterface();
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                    return null;
+                }
+            }, "getUtInterface");
+
+            if (exceptionRef.get() != null) {
+                throw exceptionRef.get();
             }
+
+            return result;
         }
 
         @Override
         public IImsEcbm getEcbmInterface() throws RemoteException {
-            synchronized (mLock) {
-                return MmTelFeature.this.getEcbmInterface();
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            IImsEcbm result = executeMethodAsyncForResult(() -> {
+                try {
+                    return MmTelFeature.this.getEcbmInterface();
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                    return null;
+                }
+            }, "getEcbmInterface");
+
+            if (exceptionRef.get() != null) {
+                throw exceptionRef.get();
             }
+
+            return result;
         }
 
         @Override
         public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException {
-            synchronized (mLock) {
-                try {
-                    MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage);
-                } catch (Exception e) {
-                    throw new RemoteException(e.getMessage());
-                }
-            }
+            executeMethodAsync(() -> MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage),
+                    "setUiTtyMode");
         }
 
         @Override
         public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
-            synchronized (mLock) {
-                return MmTelFeature.this.getMultiEndpointInterface();
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            IImsMultiEndpoint result = executeMethodAsyncForResult(() -> {
+                try {
+                    return MmTelFeature.this.getMultiEndpointInterface();
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                    return null;
+                }
+            }, "getMultiEndpointInterface");
+
+            if (exceptionRef.get() != null) {
+                throw exceptionRef.get();
             }
+
+            return result;
         }
 
         @Override
         public int queryCapabilityStatus() {
-            return MmTelFeature.this.queryCapabilityStatus().mCapabilities;
+            Integer result = executeMethodAsyncForResultNoException(() -> MmTelFeature.this
+                    .queryCapabilityStatus().mCapabilities, "queryCapabilityStatus");
+
+            if (result != null) {
+                return result.intValue();
+            } else {
+                return 0;
+            }
         }
 
         @Override
         public void addCapabilityCallback(IImsCapabilityCallback c) {
-            // no need to lock, structure already handles multithreading.
-            MmTelFeature.this.addCapabilityCallback(c);
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .addCapabilityCallback(c), "addCapabilityCallback");
         }
 
         @Override
         public void removeCapabilityCallback(IImsCapabilityCallback c) {
-            // no need to lock, structure already handles multithreading.
-            MmTelFeature.this.removeCapabilityCallback(c);
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .removeCapabilityCallback(c), "removeCapabilityCallback");
         }
 
         @Override
         public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
                 IImsCapabilityCallback c) {
-            MmTelFeature.this.requestChangeEnabledCapabilities(request, c);
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .requestChangeEnabledCapabilities(request, c),
+                    "changeCapabilitiesConfiguration");
         }
 
         @Override
         public void queryCapabilityConfiguration(int capability, int radioTech,
                 IImsCapabilityCallback c) {
-            queryCapabilityConfigurationInternal(capability, radioTech, c);
+            executeMethodAsyncNoException(() -> queryCapabilityConfigurationInternal(
+                    capability, radioTech, c), "queryCapabilityConfiguration");
         }
 
         @Override
         public void setSmsListener(IImsSmsListener l) {
-            MmTelFeature.this.setSmsListener(l);
+            executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l),
+                    "setSmsListener");
         }
 
         @Override
         public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
                 byte[] pdu) {
-            synchronized (mLock) {
-                MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu);
-            }
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .sendSms(token, messageRef, format, smsc, retry, pdu), "sendSms");
         }
 
         @Override
         public void acknowledgeSms(int token, int messageRef, int result) {
-            synchronized (mLock) {
-                MmTelFeature.this.acknowledgeSms(token, messageRef, result);
-            }
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .acknowledgeSms(token, messageRef, result), "acknowledgeSms");
         }
 
         @Override
         public void acknowledgeSmsReport(int token, int messageRef, int result) {
-            synchronized (mLock) {
-                MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result);
-            }
+            executeMethodAsyncNoException(() -> MmTelFeature.this
+                    .acknowledgeSmsReport(token, messageRef, result), "acknowledgeSmsReport");
         }
 
         @Override
         public String getSmsFormat() {
-            synchronized (mLock) {
-                return MmTelFeature.this.getSmsFormat();
-            }
+            return executeMethodAsyncForResultNoException(() -> MmTelFeature.this
+                    .getSmsFormat(), "getSmsFormat");
         }
 
         @Override
         public void onSmsReady() {
-            synchronized (mLock) {
-                MmTelFeature.this.onSmsReady();
+            executeMethodAsyncNoException(() -> MmTelFeature.this.onSmsReady(),
+                    "onSmsReady");
+        }
+
+        // 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 {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        private void executeMethodAsyncNoException(Runnable r, String errorLogName) {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(Supplier<T> r,
+                String errorLogName) throws RemoteException {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResultNoException(Supplier<T> r,
+                String errorLogName) {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "MmTelFeature Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                return null;
             }
         }
     };
@@ -672,7 +783,12 @@
     public IImsCallSession createCallSessionInterface(ImsCallProfile profile)
             throws RemoteException {
         ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile);
-        return s != null ? s.getServiceImpl() : null;
+        if (s != null) {
+            s.setDefaultExecutor(mExecutor);
+            return s.getServiceImpl();
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -713,7 +829,12 @@
      */
     protected IImsUt getUtInterface() throws RemoteException {
         ImsUtImplBase utImpl = getUt();
-        return utImpl != null ? utImpl.getInterface() : null;
+        if (utImpl != null) {
+            utImpl.setDefaultExecutor(mExecutor);
+            return utImpl.getInterface();
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -721,7 +842,12 @@
      */
     protected IImsEcbm getEcbmInterface() throws RemoteException {
         ImsEcbmImplBase ecbmImpl = getEcbm();
-        return ecbmImpl != null ? ecbmImpl.getImsEcbm() : null;
+        if (ecbmImpl != null) {
+            ecbmImpl.setDefaultExecutor(mExecutor);
+            return ecbmImpl.getImsEcbm();
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -729,7 +855,12 @@
      */
     public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
         ImsMultiEndpointImplBase multiendpointImpl = getMultiEndpoint();
-        return multiendpointImpl != null ? multiendpointImpl.getIImsMultiEndpoint() : null;
+        if (multiendpointImpl != null) {
+            multiendpointImpl.setDefaultExecutor(mExecutor);
+            return multiendpointImpl.getIImsMultiEndpoint();
+        } else {
+            return null;
+        }
     }
 
     /**
@@ -859,4 +990,16 @@
     public final IImsMmTelFeature getBinder() {
         return mImsMMTelBinder;
     }
+
+    /**
+     * Set default Executor from ImsService.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of MmTelFeature.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        if (mExecutor == null) {
+            mExecutor = executor;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 18cc37d..11cf0e3 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -70,7 +70,7 @@
         // Reference the outer class in order to have better test coverage metrics instead of
         // creating a inner class referencing the outer class directly.
         private final RcsFeature mReference;
-        private final Executor mExecutor;
+        private Executor mExecutor;
 
         RcsFeatureBinder(RcsFeature classRef, @CallbackExecutor Executor executor) {
             mReference = classRef;
@@ -259,7 +259,7 @@
         }
     }
 
-    private final Executor mExecutor;
+    private Executor mExecutor;
     private final RcsFeatureBinder mImsRcsBinder;
     private RcsCapabilityExchangeImplBase mCapabilityExchangeImpl;
     private CapabilityExchangeEventListener mCapExchangeEventListener;
@@ -270,13 +270,9 @@
      * Method stubs called from the framework will be called asynchronously. To specify the
      * {@link Executor} that the methods stubs will be called, use
      * {@link RcsFeature#RcsFeature(Executor)} instead.
-     *
-     * @deprecated Use {@link #RcsFeature(Executor)} to create the RcsFeature.
      */
-    @Deprecated
     public RcsFeature() {
         super();
-        mExecutor = Runnable::run;
         // Run on the Binder threads that call them.
         mImsRcsBinder = new RcsFeatureBinder(this, mExecutor);
     }
@@ -477,4 +473,17 @@
             return mCapabilityExchangeImpl;
         }
     }
+
+    /**
+     * Set default Executor from ImsService.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of RcsFeature.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        if (mImsRcsBinder.mExecutor == null) {
+            mExecutor = executor;
+            mImsRcsBinder.mExecutor = executor;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
index a3be8da..7a1a2af 100644
--- a/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
+++ b/telephony/java/android/telephony/ims/stub/CapabilityExchangeEventListener.java
@@ -90,6 +90,30 @@
     void onUnpublish() throws ImsException;
 
     /**
+     * Notify the framework that the ImsService has refreshed the PUBLISH
+     * internally, which has resulted in a new PUBLISH result.
+     * <p>
+     * This method must be called to notify the framework of SUCCESS (200 OK) and FAILURE (300+)
+     * codes in order to keep the AOSP stack up to date.
+     * @param reasonCode The SIP response code sent from the network.
+     * @param reasonPhrase The optional reason response from the network. If the
+     * network provided no reason with the sip code, the string should be empty.
+     * @param reasonHeaderCause The “cause” parameter of the “reason” header
+     * included in the SIP message.
+     * @param reasonHeaderText The “text” parameter of the “reason” header
+     * included in the SIP message.
+     * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is not
+     * currently connected to the framework. This can happen if the {@link RcsFeature} is not
+     * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received
+     * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare
+     * cases when the Telephony stack has crashed.
+     *
+     */
+    default void onPublishUpdated(int reasonCode, @NonNull String reasonPhrase,
+            int reasonHeaderCause, @NonNull String reasonHeaderText) throws ImsException {
+    }
+
+    /**
      * Inform the framework of an OPTIONS query from a remote device for this device's UCE
      * capabilities.
      * <p>
diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
index a3a6cb8..e810095 100644
--- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
@@ -30,12 +30,20 @@
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.telephony.ims.aidl.IImsCallSessionListener;
 import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsVideoCallProvider;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
 
 /**
  * Base implementation of IImsCallSession, which implements stub versions of the methods available.
@@ -48,6 +56,8 @@
 // DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you
 // will break other implementations of ImsCallSession maintained by other ImsServices.
 public class ImsCallSessionImplBase implements AutoCloseable {
+
+    private static final String LOG_TAG = "ImsCallSessionImplBase";
     /**
      * Notify USSD Mode.
      */
@@ -110,185 +120,235 @@
         }
     }
 
+    private Executor mExecutor = Runnable::run;
+
     // Non-final for injection by tests
     private IImsCallSession mServiceImpl = new IImsCallSession.Stub() {
         @Override
         public void close() {
-            ImsCallSessionImplBase.this.close();
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.close(), "close");
         }
 
         @Override
         public String getCallId() {
-            return ImsCallSessionImplBase.this.getCallId();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getCallId(),
+                    "getCallId");
         }
 
         @Override
         public ImsCallProfile getCallProfile() {
-            return ImsCallSessionImplBase.this.getCallProfile();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getCallProfile(),
+                    "getCallProfile");
         }
 
         @Override
         public ImsCallProfile getLocalCallProfile() {
-            return ImsCallSessionImplBase.this.getLocalCallProfile();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this
+                    .getLocalCallProfile(), "getLocalCallProfile");
         }
 
         @Override
         public ImsCallProfile getRemoteCallProfile() {
-            return ImsCallSessionImplBase.this.getRemoteCallProfile();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this
+                    .getRemoteCallProfile(), "getRemoteCallProfile");
         }
 
         @Override
         public String getProperty(String name) {
-            return ImsCallSessionImplBase.this.getProperty(name);
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getProperty(name),
+                    "getProperty");
         }
 
         @Override
         public int getState() {
-            return ImsCallSessionImplBase.this.getState();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.getState(),
+                    "getState");
         }
 
         @Override
         public boolean isInCall() {
-            return ImsCallSessionImplBase.this.isInCall();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.isInCall(),
+                    "isInCall");
         }
 
         @Override
         public void setListener(IImsCallSessionListener listener) {
-            ImsCallSessionImplBase.this.setListener(new ImsCallSessionListener(listener));
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.setListener(
+                    new ImsCallSessionListener(listener)), "setListener");
         }
 
         @Override
         public void setMute(boolean muted) {
-            ImsCallSessionImplBase.this.setMute(muted);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.setMute(muted), "setMute");
         }
 
         @Override
         public void start(String callee, ImsCallProfile profile) {
-            ImsCallSessionImplBase.this.start(callee, profile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.start(callee, profile), "start");
         }
 
         @Override
         public void startConference(String[] participants, ImsCallProfile profile) throws
                 RemoteException {
-            ImsCallSessionImplBase.this.startConference(participants, profile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.startConference(participants,
+                    profile), "startConference");
         }
 
         @Override
         public void accept(int callType, ImsStreamMediaProfile profile) {
-            ImsCallSessionImplBase.this.accept(callType, profile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.accept(callType, profile),
+                    "accept");
         }
 
         @Override
         public void deflect(String deflectNumber) {
-            ImsCallSessionImplBase.this.deflect(deflectNumber);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.deflect(deflectNumber),
+                    "deflect");
         }
 
         @Override
         public void reject(int reason) {
-            ImsCallSessionImplBase.this.reject(reason);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.reject(reason), "reject");
         }
 
         @Override
         public void transfer(@NonNull String number, boolean isConfirmationRequired) {
-            ImsCallSessionImplBase.this.transfer(number, isConfirmationRequired);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.transfer(number,
+                    isConfirmationRequired), "transfer");
         }
 
         @Override
         public void consultativeTransfer(@NonNull IImsCallSession transferToSession) {
-            ImsCallSessionImplBase otherSession = new ImsCallSessionImplBase();
-            otherSession.setServiceImpl(transferToSession);
-            ImsCallSessionImplBase.this.transfer(otherSession);
+            executeMethodAsync(() -> {
+                ImsCallSessionImplBase otherSession = new ImsCallSessionImplBase();
+                otherSession.setServiceImpl(transferToSession);
+                ImsCallSessionImplBase.this.transfer(otherSession);
+            }, "consultativeTransfer");
         }
 
         @Override
         public void terminate(int reason) {
-            ImsCallSessionImplBase.this.terminate(reason);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.terminate(reason), "terminate");
         }
 
         @Override
         public void hold(ImsStreamMediaProfile profile) {
-            ImsCallSessionImplBase.this.hold(profile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.hold(profile), "hold");
         }
 
         @Override
         public void resume(ImsStreamMediaProfile profile) {
-            ImsCallSessionImplBase.this.resume(profile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.resume(profile), "resume");
         }
 
         @Override
         public void merge() {
-            ImsCallSessionImplBase.this.merge();
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.merge(), "merge");
         }
 
         @Override
         public void update(int callType, ImsStreamMediaProfile profile) {
-            ImsCallSessionImplBase.this.update(callType, profile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.update(callType, profile),
+                    "update");
         }
 
         @Override
         public void extendToConference(String[] participants) {
-            ImsCallSessionImplBase.this.extendToConference(participants);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.extendToConference(participants),
+                    "extendToConference");
         }
 
         @Override
         public void inviteParticipants(String[] participants) {
-            ImsCallSessionImplBase.this.inviteParticipants(participants);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.inviteParticipants(participants),
+                    "inviteParticipants");
         }
 
         @Override
         public void removeParticipants(String[] participants) {
-            ImsCallSessionImplBase.this.removeParticipants(participants);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.removeParticipants(participants),
+                    "removeParticipants");
         }
 
         @Override
         public void sendDtmf(char c, Message result) {
-            ImsCallSessionImplBase.this.sendDtmf(c, result);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendDtmf(c, result), "sendDtmf");
         }
 
         @Override
         public void startDtmf(char c) {
-            ImsCallSessionImplBase.this.startDtmf(c);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.startDtmf(c), "startDtmf");
         }
 
         @Override
         public void stopDtmf() {
-            ImsCallSessionImplBase.this.stopDtmf();
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.stopDtmf(), "stopDtmf");
         }
 
         @Override
         public void sendUssd(String ussdMessage) {
-            ImsCallSessionImplBase.this.sendUssd(ussdMessage);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendUssd(ussdMessage), "sendUssd");
         }
 
         @Override
         public IImsVideoCallProvider getVideoCallProvider() {
-            return ImsCallSessionImplBase.this.getVideoCallProvider();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this
+                    .getVideoCallProvider(), "getVideoCallProvider");
         }
 
         @Override
         public boolean isMultiparty() {
-            return ImsCallSessionImplBase.this.isMultiparty();
+            return executeMethodAsyncForResult(() -> ImsCallSessionImplBase.this.isMultiparty(),
+                    "isMultiparty");
         }
 
         @Override
         public void sendRttModifyRequest(ImsCallProfile toProfile) {
-            ImsCallSessionImplBase.this.sendRttModifyRequest(toProfile);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttModifyRequest(toProfile),
+                    "sendRttModifyRequest");
         }
 
         @Override
         public void sendRttModifyResponse(boolean status) {
-            ImsCallSessionImplBase.this.sendRttModifyResponse(status);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttModifyResponse(status),
+                    "sendRttModifyResponse");
         }
 
         @Override
         public void sendRttMessage(String rttMessage) {
-            ImsCallSessionImplBase.this.sendRttMessage(rttMessage);
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRttMessage(rttMessage),
+                    "sendRttMessage");
         }
 
         @Override
         public void sendRtpHeaderExtensions(@NonNull List<RtpHeaderExtension> extensions) {
-            ImsCallSessionImplBase.this.sendRtpHeaderExtensions(
-                    new ArraySet<RtpHeaderExtension>(extensions));
+            executeMethodAsync(() -> ImsCallSessionImplBase.this.sendRtpHeaderExtensions(
+                    new ArraySet<RtpHeaderExtension>(extensions)), "sendRtpHeaderExtensions");
+        }
+
+        // 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) {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "ImsCallSessionImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(Supplier<T> r,
+                String errorLogName) {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "ImsCallSessionImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                return null;
+            }
         }
     };
 
@@ -674,4 +734,14 @@
     public void setServiceImpl(IImsCallSession serviceImpl) {
         mServiceImpl = serviceImpl;
     }
+
+    /**
+     * Set default Executor from MmTelFeature.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of ImsCallSession.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index d75da90..11fc328 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -33,12 +33,21 @@
 import com.android.ims.ImsConfig;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.util.RemoteCallbackListExt;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
 
 /**
  * Controls the modification of IMS specific configurations. For more information on the supported
@@ -81,21 +90,48 @@
         WeakReference<ImsConfigImplBase> mImsConfigImplBaseWeakReference;
         private HashMap<Integer, Integer> mProvisionedIntValue = new HashMap<>();
         private HashMap<Integer, String> mProvisionedStringValue = new HashMap<>();
+        private final Object mLock = new Object();
+        private Executor mExecutor;
 
         @VisibleForTesting
-        public ImsConfigStub(ImsConfigImplBase imsConfigImplBase) {
+        public ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Executor executor) {
+            mExecutor = executor;
             mImsConfigImplBaseWeakReference =
                     new WeakReference<ImsConfigImplBase>(imsConfigImplBase);
         }
 
         @Override
         public void addImsConfigCallback(IImsConfigCallback c) throws RemoteException {
-            getImsConfigImpl().addImsConfigCallback(c);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().addImsConfigCallback(c);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "addImsConfigCallback");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception addImsConfigCallback");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void removeImsConfigCallback(IImsConfigCallback c) throws RemoteException {
-            getImsConfigImpl().removeImsConfigCallback(c);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().removeImsConfigCallback(c);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "removeImsConfigCallback");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception removeImsConfigCallback");
+                throw exceptionRef.get();
+            }
         }
 
         /**
@@ -108,16 +144,34 @@
          * unavailable.
          */
         @Override
-        public synchronized int getConfigInt(int item) throws RemoteException {
-            if (mProvisionedIntValue.containsKey(item)) {
-                return mProvisionedIntValue.get(item);
-            } else {
-                int retVal = getImsConfigImpl().getConfigInt(item);
-                if (retVal != ImsConfig.OperationStatusConstants.UNKNOWN) {
-                    updateCachedValue(item, retVal, false);
+        public int getConfigInt(int item) throws RemoteException {
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            int retVal = executeMethodAsyncForResult(()-> {
+                int returnVal = ImsConfig.OperationStatusConstants.UNKNOWN;
+                synchronized (mLock) {
+                    if (mProvisionedIntValue.containsKey(item)) {
+                        return mProvisionedIntValue.get(item);
+                    } else {
+                        try {
+                            returnVal = getImsConfigImpl().getConfigInt(item);
+                            if (returnVal != ImsConfig.OperationStatusConstants.UNKNOWN) {
+                                mProvisionedIntValue.put(item, returnVal);
+                            }
+                        } catch (RemoteException e) {
+                            exceptionRef.set(e);
+                            return returnVal;
+                        }
+                    }
                 }
-                return retVal;
+                return returnVal;
+            }, "getConfigInt");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception getConfigString");
+                throw exceptionRef.get();
             }
+
+            return retVal;
         }
 
         /**
@@ -129,16 +183,34 @@
          * @return value in String format.
          */
         @Override
-        public synchronized String getConfigString(int item) throws RemoteException {
-            if (mProvisionedStringValue.containsKey(item)) {
-                return mProvisionedStringValue.get(item);
-            } else {
-                String retVal = getImsConfigImpl().getConfigString(item);
-                if (retVal != null) {
-                    updateCachedValue(item, retVal, false);
+        public String getConfigString(int item) throws RemoteException {
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            String retVal = executeMethodAsyncForResult(()-> {
+                String returnVal = null;
+                synchronized (mLock) {
+                    if (mProvisionedStringValue.containsKey(item)) {
+                        returnVal = mProvisionedStringValue.get(item);
+                    } else {
+                        try {
+                            returnVal = getImsConfigImpl().getConfigString(item);
+                            if (returnVal != null) {
+                                mProvisionedStringValue.put(item, returnVal);
+                            }
+                        } catch (RemoteException e) {
+                            exceptionRef.set(e);
+                            return returnVal;
+                        }
+                    }
                 }
-                return retVal;
+                return returnVal;
+            }, "getConfigString");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception getConfigString");
+                throw exceptionRef.get();
             }
+
+            return retVal;
         }
 
         /**
@@ -153,14 +225,32 @@
          * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
          */
         @Override
-        public synchronized int setConfigInt(int item, int value) throws RemoteException {
-            mProvisionedIntValue.remove(item);
-            int retVal = getImsConfigImpl().setConfig(item, value);
-            if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) {
-                updateCachedValue(item, value, true);
-            } else {
-                Log.d(TAG, "Set provision value of " + item +
-                        " to " + value + " failed with error code " + retVal);
+        public int setConfigInt(int item, int value) throws RemoteException {
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            int retVal = executeMethodAsyncForResult(()-> {
+                int returnVal = ImsConfig.OperationStatusConstants.UNKNOWN;
+                try {
+                    synchronized (mLock) {
+                        mProvisionedIntValue.remove(item);
+                        returnVal = getImsConfigImpl().setConfig(item, value);
+                        if (returnVal == ImsConfig.OperationStatusConstants.SUCCESS) {
+                            mProvisionedIntValue.put(item, value);
+                        } else {
+                            Log.d(TAG, "Set provision value of " + item
+                                    + " to " + value + " failed with error code " + returnVal);
+                        }
+                    }
+                    notifyImsConfigChanged(item, value);
+                    return returnVal;
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                    return returnVal;
+                }
+            }, "setConfigInt");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception setConfigInt");
+                throw exceptionRef.get();
             }
 
             return retVal;
@@ -178,12 +268,30 @@
          * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
          */
         @Override
-        public synchronized int setConfigString(int item, String value)
+        public int setConfigString(int item, String value)
                 throws RemoteException {
-            mProvisionedStringValue.remove(item);
-            int retVal = getImsConfigImpl().setConfig(item, value);
-            if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) {
-                updateCachedValue(item, value, true);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            int retVal = executeMethodAsyncForResult(()-> {
+                int returnVal = ImsConfig.OperationStatusConstants.UNKNOWN;
+                try {
+                    synchronized (mLock) {
+                        mProvisionedStringValue.remove(item);
+                        returnVal = getImsConfigImpl().setConfig(item, value);
+                        if (returnVal == ImsConfig.OperationStatusConstants.SUCCESS) {
+                            mProvisionedStringValue.put(item, value);
+                        }
+                    }
+                    notifyImsConfigChanged(item, value);
+                    return returnVal;
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                    return returnVal;
+                }
+            }, "setConfigString");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception setConfigInt");
+                throw exceptionRef.get();
             }
 
             return retVal;
@@ -191,7 +299,19 @@
 
         @Override
         public void updateImsCarrierConfigs(PersistableBundle bundle) throws RemoteException {
-            getImsConfigImpl().updateImsCarrierConfigs(bundle);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().updateImsCarrierConfigs(bundle);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "updateImsCarrierConfigs");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception updateImsCarrierConfigs");
+                throw exceptionRef.get();
+            }
         }
 
         private ImsConfigImplBase getImsConfigImpl() throws RemoteException {
@@ -206,13 +326,37 @@
         @Override
         public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
                 throws RemoteException {
-            getImsConfigImpl().onNotifyRcsAutoConfigurationReceived(config, isCompressed);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().onNotifyRcsAutoConfigurationReceived(config, isCompressed);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "notifyRcsAutoConfigurationReceived");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception notifyRcsAutoConfigurationReceived");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void notifyRcsAutoConfigurationRemoved()
                 throws RemoteException {
-            getImsConfigImpl().onNotifyRcsAutoConfigurationRemoved();
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().onNotifyRcsAutoConfigurationRemoved();
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "notifyRcsAutoConfigurationRemoved");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception notifyRcsAutoConfigurationRemoved");
+                throw exceptionRef.get();
+            }
         }
 
         private void notifyImsConfigChanged(int item, int value) throws RemoteException {
@@ -223,50 +367,144 @@
             getImsConfigImpl().notifyConfigChanged(item, value);
         }
 
-        protected synchronized void updateCachedValue(int item, int value, boolean notifyChange)
-        throws RemoteException {
-            mProvisionedIntValue.put(item, value);
-            if (notifyChange) {
-                notifyImsConfigChanged(item, value);
+        protected void updateCachedValue(int item, int value) {
+            synchronized (mLock) {
+                mProvisionedIntValue.put(item, value);
             }
         }
 
-        protected synchronized void updateCachedValue(int item, String value,
-                boolean notifyChange) throws RemoteException {
-            mProvisionedStringValue.put(item, value);
-            if (notifyChange) {
-                notifyImsConfigChanged(item, value);
+        protected void updateCachedValue(int item, String value) {
+            synchronized (mLock) {
+                mProvisionedStringValue.put(item, value);
             }
         }
 
         @Override
         public void addRcsConfigCallback(IRcsConfigCallback c) throws RemoteException {
-            getImsConfigImpl().addRcsConfigCallback(c);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().addRcsConfigCallback(c);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "addRcsConfigCallback");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception addRcsConfigCallback");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void removeRcsConfigCallback(IRcsConfigCallback c) throws RemoteException {
-            getImsConfigImpl().removeRcsConfigCallback(c);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().removeRcsConfigCallback(c);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "removeRcsConfigCallback");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception removeRcsConfigCallback");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void triggerRcsReconfiguration() throws RemoteException {
-            getImsConfigImpl().triggerAutoConfiguration();
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().triggerAutoConfiguration();
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "triggerRcsReconfiguration");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception triggerRcsReconfiguration");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException {
-            getImsConfigImpl().setRcsClientConfiguration(rcc);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    getImsConfigImpl().setRcsClientConfiguration(rcc);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "setRcsClientConfiguration");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception setRcsClientConfiguration");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void notifyIntImsConfigChanged(int item, int value) throws RemoteException {
-            notifyImsConfigChanged(item, value);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    notifyImsConfigChanged(item, value);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "notifyIntImsConfigChanged");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception notifyIntImsConfigChanged");
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void notifyStringImsConfigChanged(int item, String value) throws RemoteException {
-            notifyImsConfigChanged(item, value);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(()-> {
+                try {
+                    notifyImsConfigChanged(item, value);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "notifyStringImsConfigChanged");
+
+            if (exceptionRef.get() != null) {
+                Log.d(TAG, "ImsConfigImplBase Exception notifyStringImsConfigChanged");
+                throw exceptionRef.get();
+            }
+        }
+
+        // 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 {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(TAG, "ImsConfigImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(Supplier<T> r,
+                String errorLogName) throws RemoteException {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(TAG, "ImsConfigImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
         }
     }
 
@@ -303,15 +541,24 @@
     ImsConfigStub mImsConfigStub;
 
     /**
-     * Used for compatibility between older versions of the ImsService.
+     * Create a ImsConfig using the Executor specified for methods being called by the
+     * framework.
+     * @param executor The executor for the framework to use when executing the methods overridden
+     * by the implementation of ImsConfig.
+     */
+    public ImsConfigImplBase(@NonNull Executor executor) {
+        mImsConfigStub = new ImsConfigStub(this, executor);
+    }
+
+    /**
      * @hide
      */
-    public ImsConfigImplBase(Context context) {
-        mImsConfigStub = new ImsConfigStub(this);
+    public ImsConfigImplBase(@NonNull Context context) {
+        mImsConfigStub = new ImsConfigStub(this, null);
     }
 
     public ImsConfigImplBase() {
-        mImsConfigStub = new ImsConfigStub(this);
+        mImsConfigStub = new ImsConfigStub(this, null);
     }
 
     /**
@@ -427,8 +674,10 @@
      * @param value in Integer format.
      */
     public final void notifyProvisionedValueChanged(int item, int value) {
+        mImsConfigStub.updateCachedValue(item, value);
+
         try {
-            mImsConfigStub.updateCachedValue(item, value, true);
+            mImsConfigStub.notifyImsConfigChanged(item, value);
         } catch (RemoteException e) {
             Log.w(TAG, "notifyProvisionedValueChanged(int): Framework connection is dead.");
         }
@@ -443,8 +692,10 @@
      * @param value in String format.
      */
     public final void notifyProvisionedValueChanged(int item, String value) {
+        mImsConfigStub.updateCachedValue(item, value);
+
         try {
-        mImsConfigStub.updateCachedValue(item, value, true);
+            mImsConfigStub.notifyImsConfigChanged(item, value);
         } catch (RemoteException e) {
             Log.w(TAG, "notifyProvisionedValueChanged(string): Framework connection is dead.");
         }
@@ -582,4 +833,16 @@
             }
         });
     }
+
+    /**
+     * Set default Executor from ImsService.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of ImsConfig.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        if (mImsConfigStub.mExecutor == null) {
+            mImsConfigStub.mExecutor = executor;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
index 8ad40ed..84b2253 100644
--- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
@@ -16,14 +16,21 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.RemoteException;
 import android.util.Log;
 
 import com.android.ims.internal.IImsEcbm;
 import com.android.ims.internal.IImsEcbmListener;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+
 
 /**
  * Base implementation of ImsEcbm, which implements stub versions of the methods
@@ -40,10 +47,12 @@
 
     private final Object mLock = new Object();
     private IImsEcbmListener mListener;
+    private Executor mExecutor = Runnable::run;
+
     private final IImsEcbm mImsEcbm = new IImsEcbm.Stub() {
         @Override
         public void setListener(IImsEcbmListener listener) {
-            synchronized (mLock) {
+            executeMethodAsync(() -> {
                 if (mListener != null && !mListener.asBinder().isBinderAlive()) {
                     Log.w(TAG, "setListener: discarding dead Binder");
                     mListener = null;
@@ -62,12 +71,25 @@
                             + "listener");
                     mListener = listener;
                 }
-            }
+            }, "setListener");
         }
 
         @Override
         public void exitEmergencyCallbackMode() {
-            ImsEcbmImplBase.this.exitEmergencyCallbackMode();
+            executeMethodAsync(() -> ImsEcbmImplBase.this.exitEmergencyCallbackMode(),
+                    "exitEmergencyCallbackMode");
+        }
+
+        // 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) {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(TAG, "ImsEcbmImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+            }
         }
     };
 
@@ -123,4 +145,14 @@
             }
         }
     }
+
+    /**
+     * Set default Executor from MmTelFeature.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of ImsEcbm.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
index ec1c7b3..a723cd8 100644
--- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims.stub;
 
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.RemoteException;
 import android.telephony.ims.ImsExternalCallState;
@@ -23,9 +24,14 @@
 
 import com.android.ims.internal.IImsExternalCallStateListener;
 import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 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;
 
 /**
  * Base implementation of ImsMultiEndpoint, which implements stub versions of the methods
@@ -43,11 +49,13 @@
 
     private IImsExternalCallStateListener mListener;
     private final Object mLock = new Object();
+    private Executor mExecutor = Runnable::run;
+
     private final IImsMultiEndpoint mImsMultiEndpoint = new IImsMultiEndpoint.Stub() {
 
         @Override
         public void setListener(IImsExternalCallStateListener listener) throws RemoteException {
-            synchronized (mLock) {
+            executeMethodAsync(() -> {
                 if (mListener != null && !mListener.asBinder().isBinderAlive()) {
                     Log.w(TAG, "setListener: discarding dead Binder");
                     mListener = null;
@@ -67,12 +75,25 @@
                             + "listener");
                     mListener = listener;
                 }
-            }
+            }, "setListener");
         }
 
         @Override
         public void requestImsExternalCallStateInfo() throws RemoteException {
-            ImsMultiEndpointImplBase.this.requestImsExternalCallStateInfo();
+            executeMethodAsync(() -> ImsMultiEndpointImplBase.this
+                    .requestImsExternalCallStateInfo(), "requestImsExternalCallStateInfo");
+        }
+
+        // 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) {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(TAG, "ImsMultiEndpointImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+            }
         }
     };
 
@@ -108,4 +129,14 @@
     public void requestImsExternalCallStateInfo() {
         Log.d(TAG, "requestImsExternalCallStateInfo() not implemented");
     }
+
+    /**
+     * Set default Executor from MmTelFeature.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of ImsMultiEndpoint.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 02bcdec..3b151a4 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -31,10 +31,19 @@
 import android.util.Log;
 
 import com.android.internal.telephony.util.RemoteCallbackListExt;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.ArrayUtils;
 
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
 
 /**
  * Controls IMS registration for this ImsService and notifies the framework when the IMS
@@ -92,39 +101,114 @@
     // yet.
     private static final int REGISTRATION_STATE_UNKNOWN = -1;
 
+    private Executor mExecutor;
+
+    /**
+     * Create a new ImsRegistration.
+     * <p>
+     * Method stubs called from the framework will be called asynchronously. To specify the
+     * {@link Executor} that the methods stubs will be called, use
+     * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead.
+     */
+    public ImsRegistrationImplBase() {
+        super();
+    }
+
+    /**
+     * Create a ImsRegistration using the Executor specified for methods being called by the
+     * framework.
+     * @param executor The executor for the framework to use when executing the methods overridden
+     * by the implementation of ImsRegistration.
+     */
+    public ImsRegistrationImplBase(@NonNull Executor executor) {
+        super();
+        mExecutor = executor;
+    }
+
     private final IImsRegistration mBinder = new IImsRegistration.Stub() {
 
         @Override
         public @ImsRegistrationTech int getRegistrationTechnology() throws RemoteException {
-            synchronized (mLock) {
-                return (mRegistrationAttributes == null) ? REGISTRATION_TECH_NONE
-                        : mRegistrationAttributes.getRegistrationTechnology();
-            }
+            return executeMethodAsyncForResult(() -> (mRegistrationAttributes == null)
+                    ? REGISTRATION_TECH_NONE : mRegistrationAttributes.getRegistrationTechnology(),
+                    "getRegistrationTechnology");
         }
 
         @Override
         public void addRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
-            ImsRegistrationImplBase.this.addRegistrationCallback(c);
+            AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
+            executeMethodAsync(() -> {
+                try {
+                    ImsRegistrationImplBase.this.addRegistrationCallback(c);
+                } catch (RemoteException e) {
+                    exceptionRef.set(e);
+                }
+            }, "addRegistrationCallback");
+
+            if (exceptionRef.get() != null) {
+                throw exceptionRef.get();
+            }
         }
 
         @Override
         public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
-            ImsRegistrationImplBase.this.removeRegistrationCallback(c);
+            executeMethodAsync(() -> ImsRegistrationImplBase.this.removeRegistrationCallback(c),
+                    "removeRegistrationCallback");
         }
 
         @Override
         public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
-            ImsRegistrationImplBase.this.triggerFullNetworkRegistration(sipCode, sipReason);
+            executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
+                    .triggerFullNetworkRegistration(sipCode, sipReason),
+                    "triggerFullNetworkRegistration");
         }
 
         @Override
         public void triggerUpdateSipDelegateRegistration() {
-            ImsRegistrationImplBase.this.updateSipDelegateRegistration();
+            executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
+                    .updateSipDelegateRegistration(), "triggerUpdateSipDelegateRegistration");
         }
 
         @Override
         public void triggerSipDelegateDeregistration() {
-            ImsRegistrationImplBase.this.triggerSipDelegateDeregistration();
+            executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
+                    .triggerSipDelegateDeregistration(), "triggerSipDelegateDeregistration");
+        }
+
+        // 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 {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        private void executeMethodAsyncNoException(Runnable r, String errorLogName) {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(Supplier<T> r,
+                String errorLogName) throws RemoteException {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(LOG_TAG, "ImsRegistrationImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
         }
     };
 
@@ -394,4 +478,16 @@
             onSubscriberAssociatedUriChanged(c, uris);
         }
     }
+
+    /**
+     * Set default Executor from ImsService.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of Registration.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        if (mExecutor == null) {
+            mExecutor = executor;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
index eb3e8ed..11cdeed 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java
@@ -27,10 +27,17 @@
 
 import com.android.ims.internal.IImsUt;
 import com.android.ims.internal.IImsUtListener;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
 
 /**
  * Base implementation of IMS UT interface, which implements stubs. Override these methods to
@@ -119,96 +126,108 @@
      */
     public static final int INVALID_RESULT = -1;
 
+    private Executor mExecutor = Runnable::run;
+
     private final IImsUt.Stub mServiceImpl = new IImsUt.Stub() {
         private final Object mLock = new Object();
         private ImsUtListener mUtListener;
 
         @Override
         public void close() throws RemoteException {
-            ImsUtImplBase.this.close();
+            executeMethodAsync(() ->ImsUtImplBase.this.close(), "close");
         }
 
         @Override
         public int queryCallBarring(int cbType) throws RemoteException {
-            return ImsUtImplBase.this.queryCallBarring(cbType);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCallBarring(cbType),
+                    "queryCallBarring");
         }
 
         @Override
         public int queryCallForward(int condition, String number) throws RemoteException {
-            return ImsUtImplBase.this.queryCallForward(condition, number);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCallForward(
+                    condition, number), "queryCallForward");
         }
 
         @Override
         public int queryCallWaiting() throws RemoteException {
-            return ImsUtImplBase.this.queryCallWaiting();
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCallWaiting(),
+                    "queryCallWaiting");
         }
 
         @Override
         public int queryCLIR() throws RemoteException {
-            return ImsUtImplBase.this.queryCLIR();
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCLIR(), "queryCLIR");
         }
 
         @Override
         public int queryCLIP() throws RemoteException {
-            return ImsUtImplBase.this.queryCLIP();
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCLIP(), "queryCLIP");
         }
 
         @Override
         public int queryCOLR() throws RemoteException {
-            return ImsUtImplBase.this.queryCOLR();
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCOLR(), "queryCOLR");
         }
 
         @Override
         public int queryCOLP() throws RemoteException {
-            return ImsUtImplBase.this.queryCOLP();
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.queryCOLP(), "queryCOLP");
         }
 
         @Override
         public int transact(Bundle ssInfo) throws RemoteException {
-            return ImsUtImplBase.this.transact(ssInfo);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.transact(ssInfo),
+                    "transact");
         }
 
         @Override
         public int updateCallBarring(int cbType, int action, String[] barrList) throws
                 RemoteException {
-            return ImsUtImplBase.this.updateCallBarring(cbType, action, barrList);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCallBarring(
+                    cbType, action, barrList), "updateCallBarring");
         }
 
         @Override
         public int updateCallForward(int action, int condition, String number, int serviceClass,
                 int timeSeconds) throws RemoteException {
-            return ImsUtImplBase.this.updateCallForward(action, condition, number, serviceClass,
-                    timeSeconds);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCallForward(
+                    action, condition, number, serviceClass, timeSeconds), "updateCallForward");
         }
 
         @Override
         public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException {
-            return ImsUtImplBase.this.updateCallWaiting(enable, serviceClass);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCallWaiting(
+                    enable, serviceClass), "updateCallWaiting");
         }
 
         @Override
         public int updateCLIR(int clirMode) throws RemoteException {
-            return ImsUtImplBase.this.updateCLIR(clirMode);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCLIR(clirMode),
+                    "updateCLIR");
         }
 
         @Override
         public int updateCLIP(boolean enable) throws RemoteException {
-            return ImsUtImplBase.this.updateCLIP(enable);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCLIP(enable),
+                    "updateCLIP");
         }
 
         @Override
         public int updateCOLR(int presentation) throws RemoteException {
-            return ImsUtImplBase.this.updateCOLR(presentation);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCOLR(presentation),
+                    "updateCOLR");
         }
 
         @Override
         public int updateCOLP(boolean enable) throws RemoteException {
-            return ImsUtImplBase.this.updateCOLP(enable);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this.updateCOLP(enable),
+                    "updateCOLP");
         }
 
         @Override
         public void setListener(IImsUtListener listener) throws RemoteException {
-            synchronized (mLock) {
+            executeMethodAsync(() -> {
                 if (mUtListener != null
                         && !mUtListener.getListenerInterface().asBinder().isBinderAlive()) {
                     Log.w(TAG, "setListener: discarding dead Binder");
@@ -229,29 +248,59 @@
                             + "listener");
                     mUtListener = new ImsUtListener(listener);
                 }
-            }
 
-            ImsUtImplBase.this.setListener(mUtListener);
+                ImsUtImplBase.this.setListener(mUtListener);
+            }, "setListener");
         }
 
         @Override
         public int queryCallBarringForServiceClass(int cbType, int serviceClass)
                 throws RemoteException {
-            return ImsUtImplBase.this.queryCallBarringForServiceClass(cbType, serviceClass);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this
+                    .queryCallBarringForServiceClass(cbType, serviceClass),
+                    "queryCallBarringForServiceClass");
         }
 
         @Override
         public int updateCallBarringForServiceClass(int cbType, int action,
                 String[] barrList, int serviceClass) throws RemoteException {
-            return ImsUtImplBase.this.updateCallBarringForServiceClass(
-                    cbType, action, barrList, serviceClass);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this
+                    .updateCallBarringForServiceClass(cbType, action, barrList, serviceClass),
+                    "updateCallBarringForServiceClass");
         }
 
         @Override
         public int updateCallBarringWithPassword(int cbType, int action, String[] barrList,
                 int serviceClass, String password) throws RemoteException {
-            return ImsUtImplBase.this.updateCallBarringWithPassword(
-                    cbType, action, barrList, serviceClass, password);
+            return executeMethodAsyncForResult(() -> ImsUtImplBase.this
+                    .updateCallBarringWithPassword(cbType, action, barrList, serviceClass,
+                    password), "updateCallBarringWithPassword");
+        }
+
+        // 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 {
+            try {
+                CompletableFuture.runAsync(
+                        () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+            } catch (CancellationException | CompletionException e) {
+                Log.w(TAG, "ImsUtImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
+        }
+
+        private <T> T executeMethodAsyncForResult(Supplier<T> r,
+                String errorLogName) throws RemoteException {
+            CompletableFuture<T> future = CompletableFuture.supplyAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+            try {
+                return future.get();
+            } catch (ExecutionException | InterruptedException e) {
+                Log.w(TAG, "ImsUtImplBase Binder - " + errorLogName + " exception: "
+                        + e.getMessage());
+                throw new RemoteException(e.getMessage());
+            }
         }
     };
 
@@ -470,4 +519,14 @@
     public IImsUt getInterface() {
         return mServiceImpl;
     }
+
+    /**
+     * Set default Executor from MmTelFeature.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of ImsUT.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        mExecutor = executor;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
index 13ea9973..52538cb 100644
--- a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java
@@ -86,10 +86,21 @@
         }
     };
 
-    private final Executor mBinderExecutor;
+    private Executor mBinderExecutor;
     private final ArrayList<SipDelegateAidlWrapper> mDelegates = new ArrayList<>();
 
     /**
+     * Create a new SipTransport.
+     * <p>
+     * Method stubs called from the framework will be called asynchronously. To specify the
+     * {@link Executor} that the methods stubs will be called, use
+     * {@link SipTransportImplBase#SipTransportImplBase(Executor)} instead.
+     */
+    public SipTransportImplBase() {
+        super();
+    }
+
+    /**
      * Create an implementation of SipTransportImplBase.
      *
      * @param executor The executor that remote calls from the framework will be called on. This
@@ -212,4 +223,16 @@
     public ISipTransport getBinder() {
         return mSipTransportImpl;
     }
+
+    /**
+     * Set default Executor from ImsService.
+     * @param executor The default executor for the framework to use when executing the methods
+     * overridden by the implementation of SipTransport.
+     * @hide
+     */
+    public final void setDefaultExecutor(@NonNull Executor executor) {
+        if (mBinderExecutor == null) {
+            mBinderExecutor = executor;
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java
index eb59f87..81d5be8 100644
--- a/telephony/java/android/telephony/mbms/DownloadRequest.java
+++ b/telephony/java/android/telephony/mbms/DownloadRequest.java
@@ -242,8 +242,8 @@
 
     private DownloadRequest(Parcel in) {
         fileServiceId = in.readString();
-        sourceUri = in.readParcelable(getClass().getClassLoader());
-        destinationUri = in.readParcelable(getClass().getClassLoader());
+        sourceUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
+        destinationUri = in.readParcelable(getClass().getClassLoader(), android.net.Uri.class);
         subscriptionId = in.readInt();
         serializedResultIntentForApp = in.readString();
         version = in.readInt();
diff --git a/telephony/java/android/telephony/mbms/FileInfo.java b/telephony/java/android/telephony/mbms/FileInfo.java
index e52b2ce..ffd864e 100644
--- a/telephony/java/android/telephony/mbms/FileInfo.java
+++ b/telephony/java/android/telephony/mbms/FileInfo.java
@@ -55,7 +55,7 @@
     }
 
     private FileInfo(Parcel in) {
-        uri = in.readParcelable(null);
+        uri = in.readParcelable(null, android.net.Uri.class);
         mimeType = in.readString();
     }
 
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.java b/telephony/java/android/telephony/mbms/FileServiceInfo.java
index 8777e7f..0fc3be6 100644
--- a/telephony/java/android/telephony/mbms/FileServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/FileServiceInfo.java
@@ -58,7 +58,7 @@
     FileServiceInfo(Parcel in) {
         super(in);
         files = new ArrayList<FileInfo>();
-        in.readList(files, FileInfo.class.getClassLoader());
+        in.readList(files, FileInfo.class.getClassLoader(), android.telephony.mbms.FileInfo.class);
     }
 
     @Override
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java
index f78e7a6..02424ff 100644
--- a/telephony/java/android/telephony/mbms/ServiceInfo.java
+++ b/telephony/java/android/telephony/mbms/ServiceInfo.java
@@ -80,7 +80,7 @@
         }
         names = new HashMap(mapCount);
         while (mapCount-- > 0) {
-            Locale locale = (java.util.Locale) in.readSerializable();
+            Locale locale = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
             String name = in.readString();
             names.put(locale, name);
         }
@@ -91,12 +91,12 @@
         }
         locales = new ArrayList<Locale>(localesCount);
         while (localesCount-- > 0) {
-            Locale l = (java.util.Locale) in.readSerializable();
+            Locale l = (java.util.Locale) in.readSerializable(java.util.Locale.class.getClassLoader(), java.util.Locale.class);
             locales.add(l);
         }
         serviceId = in.readString();
-        sessionStartTime = (java.util.Date) in.readSerializable();
-        sessionEndTime = (java.util.Date) in.readSerializable();
+        sessionStartTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class);
+        sessionEndTime = (java.util.Date) in.readSerializable(java.util.Date.class.getClassLoader(), java.util.Date.class);
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.java b/telephony/java/android/telephony/mbms/UriPathPair.java
index 9258919..54d9d9e 100644
--- a/telephony/java/android/telephony/mbms/UriPathPair.java
+++ b/telephony/java/android/telephony/mbms/UriPathPair.java
@@ -48,8 +48,8 @@
 
     /** @hide */
     private UriPathPair(Parcel in) {
-        mFilePathUri = in.readParcelable(Uri.class.getClassLoader());
-        mContentUri = in.readParcelable(Uri.class.getClassLoader());
+        mFilePathUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
+        mContentUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
     }
 
     public static final @android.annotation.NonNull Creator<UriPathPair> CREATOR = new Creator<UriPathPair>() {
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index a900c84..1e38b92 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -313,4 +313,15 @@
 
     void setPhoneNumber(int subId, int source, String number,
             String callingPackage, String callingFeatureId);
+
+    /**
+     * Set the Usage Setting for this subscription.
+     *
+     * @param usageSetting the usage setting for this subscription
+     * @param subId the unique SubscriptionInfo index in database
+     * @param callingPackage The package making the IPC.
+     *
+     * @throws SecurityException if doesn't have MODIFY_PHONE_STATE or Carrier Privileges
+     */
+    int setUsageSetting(int usageSetting, int subId, String callingPackage);
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 6d094cb..be54cec 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,6 +67,7 @@
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.ICallForwardingInfoCallback;
+import com.android.internal.telephony.IccLogicalChannelRequest;
 import com.android.internal.telephony.IImsStateCallback;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.INumberVerificationCallback;
@@ -584,64 +585,32 @@
     void setCellInfoListRate(int rateInMillis);
 
     /**
-     * Opens a logical channel to the ICC card using the physical slot index.
-     *
-     * Input parameters equivalent to TS 27.007 AT+CCHO command.
-     *
-     * @param slotIndex The physical slot index of the target ICC card
-     * @param callingPackage the name of the package making the call.
-     * @param AID Application id. See ETSI 102.221 and 101.220.
-     * @param p2 P2 parameter (described in ISO 7816-4).
-     * @return an IccOpenLogicalChannelResponse object.
-     */
-    IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(
-            int slotIndex, String callingPackage, String AID, int p2);
-
-    /**
      * Opens a logical channel to the ICC card.
      *
      * Input parameters equivalent to TS 27.007 AT+CCHO command.
      *
-     * @param subId The subscription to use.
-     * @param callingPackage the name of the package making the call.
-     * @param AID Application id. See ETSI 102.221 and 101.220.
-     * @param p2 P2 parameter (described in ISO 7816-4).
+     * @param request the parcelable used to indicate how to open the logical channel.
      * @return an IccOpenLogicalChannelResponse object.
      */
-    IccOpenLogicalChannelResponse iccOpenLogicalChannel(
-            int subId, String callingPackage, String AID, int p2);
-
-    /**
-     * Closes a previously opened logical channel to the ICC card using the physical slot index.
-     *
-     * Input parameters equivalent to TS 27.007 AT+CCHC command.
-     *
-     * @param slotIndex The physical slot index of the target ICC card
-     * @param channel is the channel id to be closed as returned by a
-     *            successful iccOpenLogicalChannel.
-     * @return true if the channel was closed successfully.
-     */
-    boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel);
+    IccOpenLogicalChannelResponse iccOpenLogicalChannel(in IccLogicalChannelRequest request);
 
     /**
      * Closes a previously opened logical channel to the ICC card.
      *
      * Input parameters equivalent to TS 27.007 AT+CCHC command.
      *
-     * @param subId The subscription to use.
-     * @param channel is the channel id to be closed as returned by a
-     *            successful iccOpenLogicalChannel.
+     * @param request the parcelable used to indicate how to close the logical channel.
      * @return true if the channel was closed successfully.
      */
-    @UnsupportedAppUsage(trackingBug = 171933273)
-    boolean iccCloseLogicalChannel(int subId, int channel);
+    boolean iccCloseLogicalChannel(in IccLogicalChannelRequest request);
 
     /**
-     * Transmit an APDU to the ICC card over a logical channel using the physical slot index.
+     * Transmit an APDU to the ICC card over a logical channel using the physical slot index and port index.
      *
      * Input parameters equivalent to TS 27.007 AT+CGLA command.
      *
      * @param slotIndex The physical slot index of the target ICC card
+     * @param portIndex The unique index referring to a port belonging to the SIM slot
      * @param channel is the channel id to be closed as returned by a
      *            successful iccOpenLogicalChannel.
      * @param cla Class of the APDU command.
@@ -654,7 +623,7 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      */
-    String iccTransmitApduLogicalChannelBySlot(int slotIndex, int channel, int cla, int instruction,
+    String iccTransmitApduLogicalChannelByPort(int slotIndex, int portIndex, int channel, int cla, int instruction,
             int p1, int p2, int p3, String data);
 
     /**
@@ -680,11 +649,12 @@
             int p1, int p2, int p3, String data);
 
     /**
-     * Transmit an APDU to the ICC card over the basic channel using the physical slot index.
+     * Transmit an APDU to the ICC card over the basic channel using the physical slot index and port index.
      *
      * Input parameters equivalent to TS 27.007 AT+CSIM command.
      *
      * @param slotIndex The physical slot index of the target ICC card
+     * @param portIndex The unique index referring to a port belonging to the SIM slot
      * @param callingPackage the name of the package making the call.
      * @param cla Class of the APDU command.
      * @param instruction Instruction of the APDU command.
@@ -696,7 +666,7 @@
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
      */
-    String iccTransmitApduBasicChannelBySlot(int slotIndex, String callingPackage, int cla,
+    String iccTransmitApduBasicChannelByPort(int slotIndex, int portIndex, String callingPackage, int cla,
             int instruction, int p1, int p2, int p3, String data);
 
     /**
@@ -901,6 +871,8 @@
      * Perform a radio network scan and return the id of this scan.
      *
      * @param subId the id of the subscription.
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to
+     * receive fine location related information
      * @param request Defines all the configs for network scan.
      * @param messenger Callback messages will be sent using this messenger.
      * @param binder the binder object instantiated in TelephonyManager.
@@ -908,8 +880,9 @@
      * @param callingFeatureId The feature in the package
      * @return An id for this scan.
      */
-    int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger,
-            in IBinder binder, in String callingPackage, String callingFeatureId);
+    int requestNetworkScan(int subId, in boolean renounceFineLocationAccess,
+            in NetworkScanRequest request, in Messenger messenger, in IBinder binder,
+	    in String callingPackage, String callingFeatureId);
 
     /**
      * Stop an existing radio network scan.
@@ -1383,12 +1356,17 @@
     /**
      * Get the service state on specified subscription
      * @param subId Subscription id
+     * @param renounceFineLocationAccess Set this to true if the caller would not like to
+     * receive fine location related information
+     * @param renounceCoarseLocationAccess Set this to true if the caller would not like to
+     * receive coarse location related information
      * @param callingPackage The package making the call
      * @param callingFeatureId The feature in the package
      * @return Service state on specified subscription.
      */
-    ServiceState getServiceStateForSubscriber(int subId, String callingPackage,
-            String callingFeatureId);
+    ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess,
+            boolean renounceCoarseLocationAccess,
+            String callingPackage, String callingFeatureId);
 
     /**
      * Returns the URI for the per-account voicemail ringtone set in Phone settings.
@@ -2523,4 +2501,26 @@
      * Unregister an IMS connection state callback
      */
     void unregisterImsStateCallback(in IImsStateCallback cb);
+
+    /**
+     * return last known cell identity
+     * @param subId user preferred subId.
+     * @param callingPackage the name of the package making the call.
+     * @param callingFeatureId The feature in the package.
+     */
+    CellIdentity getLastKnownCellIdentity(int subId, String callingPackage,
+            String callingFeatureId);
+
+    /** Check if telephony new data stack is enabled. */
+    boolean isUsingNewDataStack();
+
+    /**
+     *  @return true if the modem service is set successfully, false otherwise.
+     */
+    boolean setModemService(in String serviceName);
+
+    /**
+     * @return the service name of the modem service which bind to.
+     */
+    String getModemService();
 }
diff --git a/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl b/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl
new file mode 100644
index 0000000..a84e752
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl
@@ -0,0 +1,52 @@
+/*
+** 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.
+*/
+
+package com.android.internal.telephony;
+
+import android.os.IBinder;
+
+/**
+ * A request to open or close a logical channel to the ICC card.
+ *
+ * @hide
+ */
+@JavaDerive(toString=true, equals=true)
+parcelable IccLogicalChannelRequest {
+
+    /** Subscription id. */
+    int subId = -1;
+
+    /** Physical slot index of the ICC card. */
+    int slotIndex = -1;
+
+    /** The unique index referring to a port belonging to the ICC card slot. */
+    int portIndex = 0;
+
+    /** Package name for the calling app, used only when open channel. */
+    @nullable String callingPackage;
+
+    /** Application id, used only when open channel. */
+    @nullable String aid;
+
+    /** The P2 parameter described in ISO 7816-4, used only when open channel. */
+    int p2 = 0;
+
+    /** Channel number */
+    int channel = -1;
+
+    /** A IBinder object for server side to check if the request client is still living. */
+    @nullable IBinder binder;
+}
diff --git a/telephony/java/com/android/internal/telephony/NetworkScanResult.java b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
index d07d77c..8b49f4b 100644
--- a/telephony/java/com/android/internal/telephony/NetworkScanResult.java
+++ b/telephony/java/com/android/internal/telephony/NetworkScanResult.java
@@ -83,7 +83,7 @@
         scanStatus = in.readInt();
         scanError = in.readInt();
         List<CellInfo> ni = new ArrayList<>();
-        in.readParcelableList(ni, Object.class.getClassLoader());
+        in.readParcelableList(ni, Object.class.getClassLoader(), android.telephony.CellInfo.class);
         networkInfos = ni;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java
index a6f0f66..1820a1d 100644
--- a/telephony/java/com/android/internal/telephony/OperatorInfo.java
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java
@@ -189,7 +189,7 @@
                             in.readString(), /*operatorAlphaLong*/
                             in.readString(), /*operatorAlphaShort*/
                             in.readString(), /*operatorNumeric*/
-                            (State) in.readSerializable(), /*state*/
+                            (State) in.readSerializable(com.android.internal.telephony.OperatorInfo.State.class.getClassLoader(), com.android.internal.telephony.OperatorInfo.State.class), /*state*/
                             in.readInt()); /*ran*/
                     return opInfo;
                 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 866fd2c..ba95841 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -530,6 +530,8 @@
     int RIL_REQUEST_GET_SLICING_CONFIG = 224;
     int RIL_REQUEST_ENABLE_VONR = 225;
     int RIL_REQUEST_IS_VONR_ENABLED = 226;
+    int RIL_REQUEST_SET_USAGE_SETTING = 227;
+    int RIL_REQUEST_GET_USAGE_SETTING = 228;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
index c717c09..1734c98 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl
@@ -45,6 +45,8 @@
         in IGetAllProfilesCallback callback);
     oneway void getProfile(String callingPackage, String cardId, String iccid,
         in IGetProfileCallback callback);
+    oneway void getEnabledProfile(String callingPackage, String cardId, int portIndex,
+        in IGetProfileCallback callback);
     oneway void disableProfile(String callingPackage, String cardId, String iccid, int portIndex,
             boolean refresh, in IDisableProfileCallback callback);
     oneway void switchToProfile(String callingPackage, String cardId, String iccid, int portIndex,
diff --git a/test-mock/api/current.txt b/test-mock/api/current.txt
index d1a68d4..c5169e5 100644
--- a/test-mock/api/current.txt
+++ b/test-mock/api/current.txt
@@ -235,6 +235,7 @@
     method @Deprecated public android.content.Intent getLaunchIntentForPackage(String);
     method @Deprecated public android.content.Intent getLeanbackLaunchIntentForPackage(String);
     method @Deprecated public String getNameForUid(int);
+    method @Deprecated public android.content.pm.PackageInfo getPackageArchiveInfo(String, int);
     method @Deprecated public int[] getPackageGids(String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public int[] getPackageGids(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated public android.content.pm.PackageInfo getPackageInfo(String, int) throws android.content.pm.PackageManager.NameNotFoundException;
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 64cb790..75f1337 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -141,20 +141,36 @@
  *
  * @param originalLayer Layer that should be visible at the start
  * @param newLayer Layer that should be visible at the end
+ * @param ignoreEntriesWithRotationLayer If entries with a visible rotation layer should be ignored
+ *      when checking the transition. If true we will not fail the assertion if a rotation layer is
+ *      visible to fill the gap between the [originalLayer] being visible and the [newLayer] being
+ *      visible.
  * @param ignoreSnapshot If the snapshot layer should be ignored during the transition
  *     (useful mostly for app launch)
+ * @param ignoreSplashscreen If the splashscreen layer should be ignored during the transition.
+ *      If true then we will allow for a splashscreen to be shown before the layer is shown,
+ *      otherwise we won't and the layer must appear immediately.
  */
 fun FlickerTestParameter.replacesLayer(
     originalLayer: FlickerComponentName,
     newLayer: FlickerComponentName,
-    ignoreSnapshot: Boolean = false
+    ignoreEntriesWithRotationLayer: Boolean = false,
+    ignoreSnapshot: Boolean = false,
+    ignoreSplashscreen: Boolean = true
 ) {
     assertLayers {
         val assertion = this.isVisible(originalLayer)
-        if (ignoreSnapshot) {
-            assertion.then()
-                    .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+
+        if (ignoreEntriesWithRotationLayer) {
+            assertion.then().isVisible(FlickerComponentName.ROTATION, isOptional = true)
         }
+        if (ignoreSnapshot) {
+            assertion.then().isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
+        }
+        if (ignoreSplashscreen) {
+            assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true)
+        }
+
         assertion.then().isVisible(newLayer)
     }
 
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 56ec80c..a7a9fe2 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
@@ -108,8 +108,12 @@
         testSpec.assertWm {
             isAppWindowVisible(imeTestApp.component)
                 .then()
+                .isAppSnapshotStartingWindowVisibleFor(testApp.component, isOptional = true)
+                .then()
                 .isAppWindowVisible(testApp.component)
                 .then()
+                .isAppSnapshotStartingWindowVisibleFor(imeTestApp.component, isOptional = true)
+                .then()
                 .isAppWindowVisible(imeTestApp.component)
         }
     }
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 fe434268f..32feccd 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
@@ -99,7 +99,11 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    override fun appLayerReplacesLauncher() {
+        // This test doesn't work in shell transitions because of b/206094140
+        assumeFalse(isShellTransitionsEnabled)
+        super.appLayerReplacesLauncher()
+    }
 
     /** {@inheritDoc} */
     @Presubmit
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 5d0d718..3914ef6 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
@@ -231,7 +231,7 @@
     @Test
     fun screenLockedStart() {
         testSpec.assertLayersStart {
-            isVisible(colorFadComponent)
+            isEmpty()
         }
     }
 
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 b104b97..b5c81bb 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
@@ -166,7 +166,8 @@
      * is replaced by [testApp], which remains visible until the end
      */
     open fun appLayerReplacesLauncher() {
-        testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component)
+        testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component,
+                ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true)
     }
 
     /**
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 4db01bf..619f5d2 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
@@ -119,7 +119,11 @@
     /** {@inheritDoc} */
     @Presubmit
     @Test
-    override fun appLayerReplacesLauncher() = super.appLayerReplacesLauncher()
+    override fun appLayerReplacesLauncher() {
+        // This test doesn't work in shell transitions because of b/206094140
+        assumeFalse(isShellTransitionsEnabled)
+        super.appLayerReplacesLauncher()
+    }
 
     /** {@inheritDoc} */
     @Presubmit
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 ded80a0..8f8951b 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
@@ -32,12 +32,14 @@
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 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.navBarLayerIsVisible
 import com.android.server.wm.flicker.navBarLayerRotatesAndScales
 import com.android.server.wm.flicker.navBarWindowIsVisible
 import com.android.server.wm.flicker.statusBarLayerIsVisible
 import com.android.server.wm.flicker.statusBarWindowIsVisible
 import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -128,6 +130,8 @@
     @Presubmit
     @Test
     fun startsWithApp1WindowsCoverFullScreen() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWmStart {
             this.frameRegion(testApp1.component).coversExactly(startDisplayBounds)
         }
@@ -140,6 +144,8 @@
     @Presubmit
     @Test
     fun startsWithApp1LayersCoverFullScreen() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersStart {
             this.visibleRegion(testApp1.component).coversExactly(startDisplayBounds)
         }
@@ -151,6 +157,8 @@
     @Presubmit
     @Test
     fun startsWithApp1WindowBeingOnTop() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWmStart {
             this.isAppWindowOnTop(testApp1.component)
         }
@@ -163,6 +171,8 @@
     @Presubmit
     @Test
     fun endsWithApp2WindowsCoveringFullScreen() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWmEnd {
             this.frameRegion(testApp2.component).coversExactly(startDisplayBounds)
         }
@@ -175,6 +185,8 @@
     @Presubmit
     @Test
     fun endsWithApp2LayersCoveringFullScreen() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayersEnd {
             this.visibleRegion(testApp2.component).coversExactly(startDisplayBounds)
         }
@@ -187,6 +199,8 @@
     @Presubmit
     @Test
     fun endsWithApp2BeingOnTop() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWmEnd {
             this.isAppWindowOnTop(testApp2.component)
         }
@@ -199,6 +213,8 @@
     @Presubmit
     @Test
     fun app2WindowBecomesAndStaysVisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWm {
             this.isAppWindowInvisible(testApp2.component)
                     .then()
@@ -215,6 +231,8 @@
     @Presubmit
     @Test
     fun app2LayerBecomesAndStaysVisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             this.isInvisible(testApp2.component)
                     .then()
@@ -229,6 +247,8 @@
     @Presubmit
     @Test
     fun app1WindowBecomesAndStaysInvisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWm {
             this.isAppWindowVisible(testApp1.component)
                     .then()
@@ -243,6 +263,8 @@
     @Presubmit
     @Test
     fun app1LayerBecomesAndStaysInvisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             this.isVisible(testApp1.component)
                     .then()
@@ -258,6 +280,8 @@
     @Presubmit
     @Test
     fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertWm {
             this.isAppWindowVisible(testApp1.component)
                     .then()
@@ -277,6 +301,8 @@
     @Presubmit
     @Test
     fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.assertLayers {
             this.isVisible(testApp1.component)
                     .then()
@@ -293,14 +319,22 @@
      */
     @Presubmit
     @Test
-    fun navBarWindowIsAlwaysVisible() = testSpec.navBarWindowIsVisible()
+    fun navBarWindowIsAlwaysVisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.navBarWindowIsVisible()
+    }
 
     /**
      * Checks that the navbar layer is visible throughout the entire transition.
      */
     @Presubmit
     @Test
-    fun navBarLayerAlwaysIsVisible() = testSpec.navBarLayerIsVisible()
+    fun navBarLayerAlwaysIsVisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.navBarLayerIsVisible()
+    }
 
     /**
      * Checks that the navbar is always in the right position and covers the expected region.
@@ -309,21 +343,33 @@
      */
     @Presubmit
     @Test
-    fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
+    fun navbarIsAlwaysInRightPosition() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.navBarLayerRotatesAndScales()
+    }
 
     /**
      * Checks that the status bar window is visible throughout the entire transition.
      */
     @Presubmit
     @Test
-    fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsVisible()
+    fun statusBarWindowIsAlwaysVisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.statusBarWindowIsVisible()
+    }
 
     /**
      * Checks that the status bar layer is visible throughout the entire transition.
      */
     @Presubmit
     @Test
-    fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
+    fun statusBarLayerIsAlwaysVisible() {
+        // This test doesn't work in shell transitions because of b/209936664
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        testSpec.statusBarLayerIsVisible()
+    }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
diff --git a/tests/MultiUser/Android.bp b/tests/MultiUser/Android.bp
new file mode 100644
index 0000000..bde309f
--- /dev/null
+++ b/tests/MultiUser/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_test {
+    name: "MultiUserTests",
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.runner",
+        "services.core",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/tests/MultiUser/AndroidManifest.xml b/tests/MultiUser/AndroidManifest.xml
new file mode 100644
index 0000000..9a25c09
--- /dev/null
+++ b/tests/MultiUser/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.test.multiuser">
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.test.multiuser">
+    </instrumentation>
+</manifest>
diff --git a/tests/MultiUser/TEST_MAPPING b/tests/MultiUser/TEST_MAPPING
new file mode 100644
index 0000000..0dbef6c
--- /dev/null
+++ b/tests/MultiUser/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "MultiUserTests"
+    }
+  ]
+}
diff --git a/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java b/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java
new file mode 100644
index 0000000..1521cc6
--- /dev/null
+++ b/tests/MultiUser/src/com/android/test/multiuser/MultiUserSettingsTests.java
@@ -0,0 +1,164 @@
+/*
+ * 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.test.multiuser;
+
+import static android.provider.Settings.Secure.FONT_WEIGHT_ADJUSTMENT;
+import static android.provider.Settings.System.FONT_SCALE;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.UserManager;
+import android.provider.Settings;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MultiUserSettingsTests {
+    private final Context mContext = getInstrumentation().getTargetContext();
+    private final ContentResolver mContentResolver = mContext.getContentResolver();
+
+    private static void waitForBroadcastIdle() throws InterruptedException {
+        final int sleepDuration = 1000;
+        final String cmdAmWaitForBroadcastIdle = "am wait-for-broadcast-idle";
+
+        Thread.sleep(sleepDuration);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .executeShellCommand(cmdAmWaitForBroadcastIdle);
+        Thread.sleep(sleepDuration);
+    }
+
+    private float getGlobalFontScale() {
+        return mContext.getResources().getConfiguration().fontScale;
+    }
+
+    private int getGlobalFontWeight() {
+        return mContext.getResources().getConfiguration().fontWeightAdjustment;
+    }
+
+    private float getFontScaleOfUser(int userId) {
+        return Settings.System.getFloatForUser(mContentResolver, FONT_SCALE, 1, userId);
+    }
+
+    private int getFontWeightOfUser(int userId) {
+        return Settings.Secure.getIntForUser(mContentResolver, FONT_WEIGHT_ADJUSTMENT, 1, userId);
+    }
+
+    private void setFontScaleOfUser(float fontScale, int userId) throws InterruptedException {
+        Settings.System.putFloatForUser(mContentResolver, FONT_SCALE, fontScale, userId);
+        waitForBroadcastIdle();
+    }
+
+    private void setFontWeightOfUser(int fontWeight, int userId) throws InterruptedException {
+        Settings.Secure.putIntForUser(mContentResolver, FONT_WEIGHT_ADJUSTMENT, fontWeight, userId);
+        waitForBroadcastIdle();
+    }
+
+    @Test
+    public void testChangingFontScaleOfABackgroundUser_shouldNotAffectUI()
+            throws InterruptedException {
+
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+        UserManager userManager = UserManager.get(mContext);
+
+        final int backgroundUserId = userManager.createUser("test_user",
+                UserManager.USER_TYPE_FULL_SECONDARY, 0).id;
+        final float oldFontScaleOfBgUser = getFontScaleOfUser(backgroundUserId);
+        final float oldGlobalFontScale = getGlobalFontScale();
+        final float newFontScaleOfBgUser = 1 + Math.max(oldGlobalFontScale, oldFontScaleOfBgUser);
+
+        try {
+            setFontScaleOfUser(newFontScaleOfBgUser, backgroundUserId);
+            final float newGlobalFontScale = getGlobalFontScale();
+            assertEquals(oldGlobalFontScale, newGlobalFontScale, 0);
+        } finally {
+            setFontScaleOfUser(oldFontScaleOfBgUser, backgroundUserId);
+            userManager.removeUser(backgroundUserId);
+        }
+    }
+
+    @Test
+    public void testChangingFontWeightOfABackgroundUser_shouldNotAffectUI()
+            throws InterruptedException {
+
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+        UserManager userManager = UserManager.get(mContext);
+
+        final int backgroundUserId = userManager.createUser("test_user",
+                UserManager.USER_TYPE_FULL_SECONDARY, 0).id;
+        final int oldFontWeightOfBgUser = getFontWeightOfUser(backgroundUserId);
+        final int oldGlobalFontWeight = getGlobalFontWeight();
+        final int newFontWeightOfBgUser = 2 * Math.max(oldGlobalFontWeight, oldFontWeightOfBgUser);
+
+        try {
+            setFontWeightOfUser(newFontWeightOfBgUser, backgroundUserId);
+            final int newGlobalFontWeight = getGlobalFontWeight();
+            assertEquals(oldGlobalFontWeight, newGlobalFontWeight);
+        } finally {
+            setFontWeightOfUser(oldFontWeightOfBgUser, backgroundUserId);
+            userManager.removeUser(backgroundUserId);
+        }
+    }
+
+    @Test
+    public void testChangingFontScaleOfTheForegroundUser_shouldAffectUI()
+            throws InterruptedException {
+
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+        final int currentUserId = mContext.getUserId();
+        final float oldFontScale = getFontScaleOfUser(currentUserId);
+        final float newFontScale = 1 + oldFontScale;
+
+        try {
+            setFontScaleOfUser(newFontScale, currentUserId);
+            final float globalFontScale = getGlobalFontScale();
+            assertEquals(newFontScale, globalFontScale, 0);
+        } finally {
+            setFontScaleOfUser(oldFontScale, currentUserId);
+        }
+    }
+
+    @Test
+    public void testChangingFontWeightOfTheForegroundUser_shouldAffectUI()
+            throws InterruptedException {
+
+        Assume.assumeTrue(UserManager.supportsMultipleUsers());
+
+        final int currentUserId = mContext.getUserId();
+        final int oldFontWeight = getFontWeightOfUser(currentUserId);
+        final int newFontWeight = 2 * oldFontWeight;
+
+        try {
+            setFontWeightOfUser(newFontWeight, currentUserId);
+            final int globalFontWeight = getGlobalFontWeight();
+            assertEquals(newFontWeight, globalFontWeight);
+        } finally {
+            setFontWeightOfUser(oldFontWeight, currentUserId);
+        }
+    }
+}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 1ab59a8..05e8bde 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -106,26 +106,26 @@
      * @param files the paths of files which might contain wildcards
      */
     private void deleteFiles(String... files) throws Exception {
-        boolean found = false;
-        for (String file : files) {
-            CommandResult result = getDevice().executeShellV2Command("ls " + file);
-            if (result.getStatus() == CommandStatus.SUCCESS) {
-                found = true;
-                break;
+        try {
+            getDevice().enableAdbRoot();
+            boolean found = false;
+            for (String file : files) {
+                CommandResult result = getDevice().executeShellV2Command("ls " + file);
+                if (result.getStatus() == CommandStatus.SUCCESS) {
+                    found = true;
+                    break;
+                }
             }
-        }
 
-        if (found) {
-            try {
-                getDevice().enableAdbRoot();
+            if (found) {
                 getDevice().remountSystemWritable();
                 for (String file : files) {
                     getDevice().executeShellCommand("rm -rf " + file);
                 }
-            } finally {
-                getDevice().disableAdbRoot();
+                getDevice().reboot();
             }
-            getDevice().reboot();
+        } finally {
+            getDevice().disableAdbRoot();
         }
     }
 
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 926bf1b..f06fa81e 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -112,6 +112,10 @@
      * @param files the paths of files which might contain wildcards
      */
     private void deleteFiles(String... files) throws Exception {
+        if (!getDevice().isAdbRoot()) {
+            getDevice().enableAdbRoot();
+        }
+
         boolean found = false;
         for (String file : files) {
             CommandResult result = getDevice().executeShellV2Command("ls " + file);
@@ -122,9 +126,6 @@
         }
 
         if (found) {
-            if (!getDevice().isAdbRoot()) {
-                getDevice().enableAdbRoot();
-            }
             getDevice().remountSystemWritable();
             for (String file : files) {
                 getDevice().executeShellCommand("rm -rf " + file);
diff --git a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
index f7d36970..476be44 100644
--- a/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnCellUnderlyingNetworkPriorityTest.java
@@ -36,7 +36,7 @@
         return new VcnCellUnderlyingNetworkPriority.Builder()
                 .setNetworkQuality(NETWORK_QUALITY_OK)
                 .setAllowMetered(true /* allowMetered */)
-                .setAllowedPlmnIds(ALLOWED_PLMN_IDS)
+                .setAllowedOperatorPlmnIds(ALLOWED_PLMN_IDS)
                 .setAllowedSpecificCarrierIds(ALLOWED_CARRIER_IDS)
                 .setAllowRoaming(true /* allowRoaming */)
                 .setRequireOpportunistic(true /* requireOpportunistic */)
@@ -48,7 +48,7 @@
         final VcnCellUnderlyingNetworkPriority networkPriority = getTestNetworkPriority();
         assertEquals(NETWORK_QUALITY_OK, networkPriority.getNetworkQuality());
         assertTrue(networkPriority.allowMetered());
-        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedPlmnIds());
+        assertEquals(ALLOWED_PLMN_IDS, networkPriority.getAllowedOperatorPlmnIds());
         assertEquals(ALLOWED_CARRIER_IDS, networkPriority.getAllowedSpecificCarrierIds());
         assertTrue(networkPriority.allowRoaming());
         assertTrue(networkPriority.requireOpportunistic());
@@ -60,7 +60,7 @@
                 new VcnCellUnderlyingNetworkPriority.Builder().build();
         assertEquals(NETWORK_QUALITY_ANY, networkPriority.getNetworkQuality());
         assertFalse(networkPriority.allowMetered());
-        assertEquals(new HashSet<String>(), networkPriority.getAllowedPlmnIds());
+        assertEquals(new HashSet<String>(), networkPriority.getAllowedOperatorPlmnIds());
         assertEquals(new HashSet<Integer>(), networkPriority.getAllowedSpecificCarrierIds());
         assertFalse(networkPriority.allowRoaming());
         assertFalse(networkPriority.requireOpportunistic());
diff --git a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
index 724c33f..377f526 100644
--- a/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
+++ b/tests/vcn/java/android/net/vcn/VcnGatewayConnectionConfigTest.java
@@ -17,6 +17,8 @@
 package android.net.vcn;
 
 import static android.net.ipsec.ike.IkeSessionParams.IKE_OPTION_MOBIKE;
+import static android.net.vcn.VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES;
+import static android.net.vcn.VcnGatewayConnectionConfig.UNDERLYING_NETWORK_PRIORITIES_KEY;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -30,6 +32,7 @@
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
 import android.net.vcn.persistablebundleutils.IkeSessionParamsUtilsTest;
 import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtilsTest;
+import android.os.PersistableBundle;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -230,6 +233,16 @@
         assertEquals(config, new VcnGatewayConnectionConfig(config.toPersistableBundle()));
     }
 
+    @Test
+    public void testParsePersistableBundleWithoutVcnUnderlyingNetworkPriorities() {
+        PersistableBundle configBundle = buildTestConfig().toPersistableBundle();
+        configBundle.putPersistableBundle(UNDERLYING_NETWORK_PRIORITIES_KEY, null);
+
+        final VcnGatewayConnectionConfig config = new VcnGatewayConnectionConfig(configBundle);
+        assertEquals(
+                DEFAULT_UNDERLYING_NETWORK_PRIORITIES, config.getVcnUnderlyingNetworkPriorities());
+    }
+
     private static IkeTunnelConnectionParams buildTunnelConnectionParams(String ikePsk) {
         final IkeSessionParams ikeParams =
                 IkeSessionParamsUtilsTest.createBuilderMinimum()
@@ -271,4 +284,40 @@
         assertNotEquals(tunnelParams, anotherTunnelParams);
         assertNotEquals(config, anotherConfig);
     }
+
+    private static VcnGatewayConnectionConfig buildTestConfigWithVcnUnderlyingNetworkPriorities(
+            LinkedHashSet<VcnUnderlyingNetworkPriority> networkPriorities) {
+        return buildTestConfigWithExposedCaps(
+                new VcnGatewayConnectionConfig.Builder(
+                                "buildTestConfigWithVcnUnderlyingNetworkPriorities",
+                                TUNNEL_CONNECTION_PARAMS)
+                        .setVcnUnderlyingNetworkPriorities(networkPriorities),
+                EXPOSED_CAPS);
+    }
+
+    @Test
+    public void testVcnUnderlyingNetworkPrioritiesEquality() throws Exception {
+        final VcnGatewayConnectionConfig config =
+                buildTestConfigWithVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_PRIORITIES);
+
+        final LinkedHashSet<VcnUnderlyingNetworkPriority> networkPrioritiesEqual =
+                new LinkedHashSet();
+        networkPrioritiesEqual.add(VcnCellUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        networkPrioritiesEqual.add(VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        final VcnGatewayConnectionConfig configEqual =
+                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesEqual);
+
+        final LinkedHashSet<VcnUnderlyingNetworkPriority> networkPrioritiesNotEqual =
+                new LinkedHashSet();
+        networkPrioritiesNotEqual.add(
+                VcnWifiUnderlyingNetworkPriorityTest.getTestNetworkPriority());
+        final VcnGatewayConnectionConfig configNotEqual =
+                buildTestConfigWithVcnUnderlyingNetworkPriorities(networkPrioritiesNotEqual);
+
+        assertEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesEqual);
+        assertEquals(config, configEqual);
+
+        assertNotEquals(UNDERLYING_NETWORK_PRIORITIES, networkPrioritiesNotEqual);
+        assertNotEquals(config, configNotEqual);
+    }
 }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
index 15de226..e547400 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java
@@ -118,6 +118,8 @@
 
     @Test
     public void testNullNetworkDoesNotTriggerDisconnect() throws Exception {
+        doReturn(false).when(mDeps).isAirplaneModeOn(any());
+
         mGatewayConnection
                 .getUnderlyingNetworkControllerCallback()
                 .onSelectedUnderlyingNetworkChanged(null);
@@ -129,6 +131,19 @@
     }
 
     @Test
+    public void testNullNetworkAirplaneModeDisconnects() throws Exception {
+        doReturn(true).when(mDeps).isAirplaneModeOn(any());
+
+        mGatewayConnection
+                .getUnderlyingNetworkControllerCallback()
+                .onSelectedUnderlyingNetworkChanged(null);
+        mTestLooper.dispatchAll();
+
+        assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState());
+        verify(mIkeSession).kill();
+    }
+
+    @Test
     public void testNewNetworkTriggersMigration() throws Exception {
         mGatewayConnection
                 .getUnderlyingNetworkControllerCallback()
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 8a0af2d..5628321 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -179,7 +179,7 @@
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
-                .newUnderlyingNetworkController(any(), any(), any(), any());
+                .newUnderlyingNetworkController(any(), any(), any(), any(), any());
         doReturn(mWakeLock)
                 .when(mDeps)
                 .newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
new file mode 100644
index 0000000..46a614f
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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.vcn.routeselection;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.calculatePriorityClass;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
+import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkPriority;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnWifiUnderlyingNetworkPriority;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class NetworkPriorityClassifierTest {
+    private static final String SSID = "TestWifi";
+    private static final String SSID_OTHER = "TestWifiOther";
+    private static final String PLMN_ID = "123456";
+    private static final String PLMN_ID_OTHER = "234567";
+
+    private static final int SUB_ID = 1;
+    private static final int WIFI_RSSI = -60;
+    private static final int WIFI_RSSI_HIGH = -50;
+    private static final int WIFI_RSSI_LOW = -80;
+    private static final int CARRIER_ID = 1;
+    private static final int CARRIER_ID_OTHER = 2;
+
+    private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+
+    private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                    .setSignalStrength(WIFI_RSSI)
+                    .setSsid(SSID)
+                    .build();
+
+    private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+            new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+    private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
+            new NetworkCapabilities.Builder()
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .setSubscriptionIds(Set.of(SUB_ID))
+                    .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+                    .build();
+
+    private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
+
+    @Mock private Network mNetwork;
+    @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+    @Mock private TelephonyManager mTelephonyManager;
+
+    private TestLooper mTestLooper;
+    private VcnContext mVcnContext;
+    private UnderlyingNetworkRecord mWifiNetworkRecord;
+    private UnderlyingNetworkRecord mCellNetworkRecord;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        final Context mockContext = mock(Context.class);
+        mTestLooper = new TestLooper();
+        mVcnContext =
+                spy(
+                        new VcnContext(
+                                mockContext,
+                                mTestLooper.getLooper(),
+                                mock(VcnNetworkProvider.class),
+                                false /* isInTestMode */));
+        doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+        mWifiNetworkRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        WIFI_NETWORK_CAPABILITIES,
+                        LINK_PROPERTIES,
+                        false /* isBlocked */);
+
+        mCellNetworkRecord =
+                new UnderlyingNetworkRecord(
+                        mNetwork,
+                        CELL_NETWORK_CAPABILITIES,
+                        LINK_PROPERTIES,
+                        false /* isBlocked */);
+
+        setupSystemService(
+                mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+        when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
+        when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
+        when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+    }
+
+    @Test
+    public void testMatchWithoutNotMeteredBit() {
+        final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(false /* allowMetered */)
+                        .build();
+
+        assertFalse(
+                checkMatchesPriorityRule(
+                        mVcnContext,
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        null /* currentlySelecetd */,
+                        null /* carrierConfig */));
+    }
+
+    private void verifyMatchWifi(
+            boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) {
+        final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .build();
+        final UnderlyingNetworkRecord selectedNetworkRecord =
+                isSelectedNetwork ? mWifiNetworkRecord : null;
+        assertEquals(
+                expectMatch,
+                checkMatchesWifiPriorityRule(
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        selectedNetworkRecord,
+                        carrierConfig));
+    }
+
+    @Test
+    public void testMatchSelectedWifi() {
+        verifyMatchWifi(
+                true /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchSelectedWifiBelowRssiThreshold() {
+        final PersistableBundle carrierConfig = new PersistableBundle();
+        carrierConfig.putInt(
+                VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+        carrierConfig.putInt(
+                VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+
+        verifyMatchWifi(true /* isSelectedNetwork */, carrierConfig, false /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchUnselectedWifi() {
+        verifyMatchWifi(
+                false /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchUnselectedWifiBelowRssiThreshold() {
+        final PersistableBundle carrierConfig = new PersistableBundle();
+        carrierConfig.putInt(
+                VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+
+        verifyMatchWifi(false /* isSelectedNetwork */, carrierConfig, false /* expectMatch */);
+    }
+
+    private void verifyMatchWifiWithSsid(boolean useMatchedSsid, boolean expectMatch) {
+        final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER;
+        final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+                new VcnWifiUnderlyingNetworkPriority.Builder()
+                        .setNetworkQuality(NETWORK_QUALITY_OK)
+                        .setAllowMetered(true /* allowMetered */)
+                        .setSsid(nwPrioritySsid)
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesWifiPriorityRule(
+                        wifiNetworkPriority,
+                        mWifiNetworkRecord,
+                        null /* currentlySelecetd */,
+                        null /* carrierConfig */));
+    }
+
+    @Test
+    public void testMatchWifiWithSsid() {
+        verifyMatchWifiWithSsid(true /* useMatchedSsid */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchWifiFailWithWrongSsid() {
+        verifyMatchWifiWithSsid(false /* useMatchedSsid */, false /* expectMatch */);
+    }
+
+    private static VcnCellUnderlyingNetworkPriority.Builder getCellNetworkPriorityBuilder() {
+        return new VcnCellUnderlyingNetworkPriority.Builder()
+                .setNetworkQuality(NETWORK_QUALITY_OK)
+                .setAllowMetered(true /* allowMetered */)
+                .setAllowRoaming(true /* allowRoaming */);
+    }
+
+    @Test
+    public void testMatchMacroCell() {
+        assertTrue(
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        getCellNetworkPriorityBuilder().build(),
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    @Test
+    public void testMatchOpportunisticCell() {
+        final VcnCellUnderlyingNetworkPriority opportunisticCellNetworkPriority =
+                getCellNetworkPriorityBuilder()
+                        .setRequireOpportunistic(true /* requireOpportunistic */)
+                        .build();
+
+        when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true);
+        when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>());
+
+        assertTrue(
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        opportunisticCellNetworkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    private void verifyMatchMacroCellWithAllowedPlmnIds(
+            boolean useMatchedPlmnId, boolean expectMatch) {
+        final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                getCellNetworkPriorityBuilder()
+                        .setAllowedOperatorPlmnIds(Set.of(networkPriorityPlmnId))
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        networkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    @Test
+    public void testMatchMacroCellWithAllowedPlmnIds() {
+        verifyMatchMacroCellWithAllowedPlmnIds(true /* useMatchedPlmnId */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchMacroCellFailWithDisallowedPlmnIds() {
+        verifyMatchMacroCellWithAllowedPlmnIds(
+                false /* useMatchedPlmnId */, false /* expectMatch */);
+    }
+
+    private void verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+            boolean useMatchedCarrierId, boolean expectMatch) {
+        final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER;
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                getCellNetworkPriorityBuilder()
+                        .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId))
+                        .build();
+
+        assertEquals(
+                expectMatch,
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        networkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    @Test
+    public void testMatchMacroCellWithAllowedSpecificCarrierIds() {
+        verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+                true /* useMatchedCarrierId */, true /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchMacroCellFailWithDisallowedSpecificCarrierIds() {
+        verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+                false /* useMatchedCarrierId */, false /* expectMatch */);
+    }
+
+    @Test
+    public void testMatchWifiFailWithoutNotRoamingBit() {
+        final VcnCellUnderlyingNetworkPriority networkPriority =
+                getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+
+        assertFalse(
+                checkMatchesCellPriorityRule(
+                        mVcnContext,
+                        networkPriority,
+                        mCellNetworkRecord,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot));
+    }
+
+    private void verifyCalculatePriorityClass(
+            UnderlyingNetworkRecord networkRecord, int expectedIndex) {
+        final int priorityIndex =
+                calculatePriorityClass(
+                        mVcnContext,
+                        networkRecord,
+                        VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        null /* currentlySelected */,
+                        null /* carrierConfig */);
+
+        assertEquals(expectedIndex, priorityIndex);
+    }
+
+    @Test
+    public void testCalculatePriorityClass() throws Exception {
+        verifyCalculatePriorityClass(mCellNetworkRecord, 2);
+    }
+
+    @Test
+    public void testCalculatePriorityClassFailToMatchAny() throws Exception {
+        final NetworkCapabilities nc =
+                new NetworkCapabilities.Builder()
+                        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                        .setSignalStrength(WIFI_RSSI_LOW)
+                        .setSsid(SSID)
+                        .build();
+        final UnderlyingNetworkRecord wifiNetworkRecord =
+                new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */);
+
+        verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY);
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index c954cb8..fad9669 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -42,6 +42,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
 import android.os.ParcelUuid;
 import android.os.test.TestLooper;
 import android.telephony.CarrierConfigManager;
@@ -145,7 +146,11 @@
 
         mUnderlyingNetworkController =
                 new UnderlyingNetworkController(
-                        mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
+                        mVcnContext,
+                        VcnGatewayConnectionConfigTest.buildTestConfig(),
+                        SUB_GROUP,
+                        mSubscriptionSnapshot,
+                        mNetworkControllerCb);
     }
 
     private void resetVcnContext() {
@@ -153,7 +158,8 @@
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
     }
 
-    private static LinkProperties getLinkPropertiesWithName(String iface) {
+    // Package private for use in NetworkPriorityClassifierTest
+    static LinkProperties getLinkPropertiesWithName(String iface) {
         LinkProperties linkProperties = new LinkProperties();
         linkProperties.setInterfaceName(iface);
         return linkProperties;
@@ -182,7 +188,11 @@
                         true /* isInTestMode */);
 
         new UnderlyingNetworkController(
-                vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
+                vcnContext,
+                VcnGatewayConnectionConfigTest.buildTestConfig(),
+                SUB_GROUP,
+                mSubscriptionSnapshot,
+                mNetworkControllerCb);
 
         verify(cm)
                 .registerNetworkCallback(
@@ -345,6 +355,17 @@
         return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
     }
 
+    private static NetworkCapabilities buildResponseNwCaps(
+            NetworkCapabilities requestNetworkCaps, Set<Integer> netCapsSubIds) {
+        final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+                new TelephonyNetworkSpecifier.Builder()
+                        .setSubscriptionId(netCapsSubIds.iterator().next())
+                        .build();
+        return new NetworkCapabilities.Builder(requestNetworkCaps)
+                .setNetworkSpecifier(telephonyNetworkSpecifier)
+                .build();
+    }
+
     private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback(
             NetworkCapabilities networkCapabilities) {
         verify(mConnectivityManager)
@@ -355,14 +376,17 @@
 
         UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.getValue();
         cb.onAvailable(mNetwork);
-        cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(networkCapabilities, INITIAL_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
         cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
         cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        networkCapabilities,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -373,12 +397,14 @@
     public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        UPDATED_NETWORK_CAPABILITIES,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -393,7 +419,7 @@
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
+                        buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
                         UPDATED_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -403,19 +429,21 @@
     public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(SUSPENDED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        SUSPENDED_NETWORK_CAPABILITIES,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
-        cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
@@ -425,19 +453,21 @@
         UnderlyingNetworkListener cb =
                 verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
 
-        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        final NetworkCapabilities responseNetworkCaps =
+                buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
+                        responseNetworkCaps,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
         // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
         // change.
-        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
         verify(mNetworkControllerCb, times(1))
                 .onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
@@ -451,7 +481,7 @@
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
                         mNetwork,
-                        INITIAL_NETWORK_CAPABILITIES,
+                        buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
                         INITIAL_LINK_PROPERTIES,
                         true /* isBlocked */);
         verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -470,7 +500,8 @@
     public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(
+                mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
 
         // Verify no more calls to the UnderlyingNetworkControllerCallback when the
         // UnderlyingNetworkRecord does not actually change
@@ -482,7 +513,8 @@
         UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
         mUnderlyingNetworkController.teardown();
 
-        cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+        cb.onCapabilitiesChanged(
+                mNetwork, buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
 
         // Verify that the only call was during onAvailable()
         verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index b46a125..d432341 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -544,6 +544,7 @@
   application_action["activity-alias"] = component_action;
   application_action["service"] = component_action;
   application_action["receiver"] = component_action;
+  application_action["apex-system-service"] = component_action;
 
   // Provider actions.
   application_action["provider"] = component_action;
@@ -587,10 +588,21 @@
             child_el->name == "provider" || child_el->name == "receiver" ||
             child_el->name == "service") {
           FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", child_el);
+          continue;
         }
 
         if (child_el->name == "activity-alias") {
           FullyQualifyClassName(original_package, xml::kSchemaAndroid, "targetActivity", child_el);
+          continue;
+        }
+
+        if (child_el->name == "processes") {
+          for (xml::Element* grand_child_el : child_el->GetChildElements()) {
+            if (grand_child_el->name == "process") {
+              FullyQualifyClassName(original_package, xml::kSchemaAndroid, "name", grand_child_el);
+            }
+          }
+          continue;
         }
       }
     }
diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp
index 98c0e69..3e12971 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-6.0",
-        "asm-commons-6.0",
-        "asm-tree-6.0",
-        "asm-analysis-6.0",
+        "asm-7.0",
+        "asm-commons-7.0",
+        "asm-tree-7.0",
+        "asm-analysis-7.0",
         "guava-21.0",
     ],
 }
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
index 1002c88..335b3f8 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetStateAnalysis.java
@@ -13,8 +13,6 @@
  */
 package lockedregioncodeinjection;
 
-import java.util.ArrayList;
-import java.util.List;
 import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.AbstractInsnNode;
 import org.objectweb.asm.tree.MethodInsnNode;
@@ -22,6 +20,9 @@
 import org.objectweb.asm.tree.analysis.BasicInterpreter;
 import org.objectweb.asm.tree.analysis.BasicValue;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * A simple dataflow analysis to determine if the operands on the stack must be one of target lock
  * class type.
@@ -31,6 +32,7 @@
     private final List<LockTarget> targetLocks;
 
     public LockTargetStateAnalysis(List<LockTarget> targetLocks) {
+        super(Utils.ASM_VERSION);
         this.targetLocks = targetLocks;
     }
 
diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
index 219c2b3..f1e84b1 100644
--- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
+++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java
@@ -13,13 +13,14 @@
  */
 package lockedregioncodeinjection;
 
+import org.objectweb.asm.Opcodes;
+
 import java.util.ArrayList;
 import java.util.List;
-import org.objectweb.asm.Opcodes;
 
 public class Utils {
 
-    public static final int ASM_VERSION = Opcodes.ASM6;
+    public static final int ASM_VERSION = Opcodes.ASM7;
 
     /**
      * Reads a comma separated configuration similar to the Jack definition.
diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
index c408b9e..31fa0bf 100644
--- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
+++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java
@@ -30,7 +30,7 @@
  * rm -fr out/*
  *
  * # Make booster
- * javac -cp lib/asm-6.0_BETA.jar:lib/asm-commons-6.0_BETA.jar:lib/asm-tree-6.0_BETA.jar:lib/asm-analysis-6.0_BETA.jar:lib/guava-21.0.jar src&#47;*&#47;*.java -d out/
+ * javac -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar src&#47;*&#47;*.java -d out/
  * pushd out
  * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main *&#47;*.class
  * popd
@@ -43,7 +43,7 @@
  * popd
  *
  * # Run tool on unit tests.
- * java -ea -cp lib/asm-6.0_BETA.jar:lib/asm-commons-6.0_BETA.jar:lib/asm-tree-6.0_BETA.jar:lib/asm-analysis-6.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \
+ * java -ea -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \
  *     lockedregioncodeinjection.Main \
  *     -i out/test_input.jar -o out/test_output.jar \
  *     --targets 'Llockedregioncodeinjection/TestTarget;' \